From d260f724fa8adc8eb4c62792b24bf117fa1b604c Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 29 Jan 2021 18:14:17 +0100 Subject: [PATCH 01/40] =?UTF-8?q?=C2=9BAdd=20refactor=20prototype.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- refactor_prototype.jl | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 refactor_prototype.jl diff --git a/refactor_prototype.jl b/refactor_prototype.jl new file mode 100644 index 00000000..536b0a0f --- /dev/null +++ b/refactor_prototype.jl @@ -0,0 +1,79 @@ +using Latexify +nested(ex::Expr) = true +args(ex::Expr) = ex.args[2:end] +op(ex::Expr) = ex.args[1] +head(ex::Expr) = ex.head + +nested(::Any) = false + + +value(::Val{T}) where T = T +isiterable(x) = hasmethod(iterate, (typeof(x),)) +ValUnion(x) = isiterable(x) ? Union{typeof.(Val.(x))...} : Val{x} +ValUnion(a, b, c...) = ValUnion((a, b, c...)) + + +surround(x) = "\\left( $x \\right)" +function strip_surround(x) + m = match(r"^\\left\( (.*) \\right\)$", x) + return isnothing(m) ? x : m.captures[1] +end + + + +### Fallback method for functions of type f(x...) +lf(func, ::Any, args) = "$(value(func))\\left($(join(args, ", "))\\right)" +lf(func::ValUnion(Latexify.trigonometric_functions), ::Any, args) = "\\$(value(func))\\left($(join(args, ", "))\\right)" +# lf(func::Val{:sin}, ::Any, args) = "$(value(func))\\left($(join(args, ", "))\\right)" + + +function lf(op::Val{:+}, ::ValUnion(:*, :^), args) + surround(join(args, " $(value(op)) ")) +end + +lf(op::Val{:+}, prevop, args) = join(args, " + ") + +# ### router functions +lf(::Val{:call}, op, prevop, args) = lf(op, prevop, args) + +# ### :call functions +lf(op::Val{:*}, prevop, args; mul_symb=" \\cdot ", kw...) = + join(args, string(mul_symb)) +lf(op::Val{:-}, prevop, args; kw...) = + length(args) == 1 ? "- $(args[1])" : join(args, " - ") + + +function lf(op::Val{:^}, ::Any, args; kw...) + pattern = r"^\\(\w*)" + m = match(pattern, args[1]) + if !isnothing(m) && Symbol(m.captures[1]) ∈ Latexify.trigonometric_functions + replace(args[1], pattern=>"$(m.match)^{$(args[2])}" ) + else + "$(args[1])^{$(strip_surround(args[2]))}" + end +end + +# # function lf(::Val{:^}, prevop::ValUnion(:sin, :cos), args; kw...) +# function lf(::T, prevop::Val{:^}, args; kw...) where T <: ValUnion(:sin, :cos) +# display(args) +# return "sin" +# end + +lf(op::Val{:/}, prevop, args; kw...) = + "\\frac{$(args[1])}{$(args[2])}" +lf(op::Val{:revealargs}, prevop, args; kw...) = args + + +# ### non :call functions +lf(::Val{:ref}, op, prevop, args; kw...) = "$(value(op))\\left[$(join(args, ", "))\\right]" +lf(::Val{:latexifymerge}, op, prevop, args; kw...) = string(value(op)) * join(args, "") + + +lf(head::Symbol, op::Symbol, prevop::Symbol, args) = lf(Val{head}(), Val{op}(), Val{prevop}(), args) +lf(head::Symbol, op::Symbol, prevop, args) = lf(Val{head}(), Val{op}(), prevop, args) +lf(ex, prevop) = lf(head(ex), op(ex), prevop, args(ex)) + + +dive(ex, prevop=nothing) = dive(Val{nested(ex)}(), ex, prevop) +dive(::Val{false}, ex, prevop) = string(ex) +dive(::Val{true}, ex, prevop) = lf(head(ex), op(ex), prevop, dive.(args(ex), op(ex))) From f39f27521e0380d527658c94f5eb0eb40dc9bffa Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sat, 30 Jan 2021 11:44:41 +0100 Subject: [PATCH 02/40] Revise refactor prototype. --- refactor_prototype.jl | 52 +++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/refactor_prototype.jl b/refactor_prototype.jl index 536b0a0f..76267b09 100644 --- a/refactor_prototype.jl +++ b/refactor_prototype.jl @@ -4,6 +4,15 @@ args(ex::Expr) = ex.args[2:end] op(ex::Expr) = ex.args[1] head(ex::Expr) = ex.head + +arguments(ex::Expr) = ex.args[2:end] +operation(ex::Expr) = ex.args[1] +head(ex::Expr) = ex.head +arguments(::Any) = nothing +operation(::Any) = nothing +head(::Any) = nothing + + nested(::Any) = false @@ -22,58 +31,53 @@ end ### Fallback method for functions of type f(x...) -lf(func, ::Any, args) = "$(value(func))\\left($(join(args, ", "))\\right)" -lf(func::ValUnion(Latexify.trigonometric_functions), ::Any, args) = "\\$(value(func))\\left($(join(args, ", "))\\right)" +lf(op, ::Any, args) = "$(value(op))\\left($(join(lf.(args, op), ", "))\\right)" +lf(op::ValUnion(Latexify.trigonometric_functions), ::Any, args) = "\\$(value(func))\\left($(join(lf.(args, op), ", "))\\right)" # lf(func::Val{:sin}, ::Any, args) = "$(value(func))\\left($(join(args, ", "))\\right)" function lf(op::Val{:+}, ::ValUnion(:*, :^), args) - surround(join(args, " $(value(op)) ")) + surround(join(lf.(args, op), " $(value(op)) ")) end -lf(op::Val{:+}, prevop, args) = join(args, " + ") +lf(op::Val{:+}, prevop, args) = join(lf.(args, op), " + ") # ### router functions lf(::Val{:call}, op, prevop, args) = lf(op, prevop, args) # ### :call functions lf(op::Val{:*}, prevop, args; mul_symb=" \\cdot ", kw...) = - join(args, string(mul_symb)) + join(lf.(args, op), string(mul_symb)) lf(op::Val{:-}, prevop, args; kw...) = - length(args) == 1 ? "- $(args[1])" : join(args, " - ") + length(args) == 1 ? "- $(lf(args[1], op))" : join(lf.(args, op), " - ") function lf(op::Val{:^}, ::Any, args; kw...) - pattern = r"^\\(\w*)" - m = match(pattern, args[1]) - if !isnothing(m) && Symbol(m.captures[1]) ∈ Latexify.trigonometric_functions - replace(args[1], pattern=>"$(m.match)^{$(args[2])}" ) + if operation(args[1]) in Latexify.trigonometric_functions + fsym = args[1] + fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") + "$fstring^{$(lf(args[2], op))}\\left( $(join(lf.(arguments(args[1]), operation(args[1])), ", ")) \\right)" else - "$(args[1])^{$(strip_surround(args[2]))}" + "$(lf(args[1], op))^{$(lf(args[2], Val{:NoSurround}()))}" end end -# # function lf(::Val{:^}, prevop::ValUnion(:sin, :cos), args; kw...) -# function lf(::T, prevop::Val{:^}, args; kw...) where T <: ValUnion(:sin, :cos) -# display(args) -# return "sin" -# end - lf(op::Val{:/}, prevop, args; kw...) = - "\\frac{$(args[1])}{$(args[2])}" + "\\frac{$(lf(args[1], op))}{$(lf(args[2], op))}" lf(op::Val{:revealargs}, prevop, args; kw...) = args # ### non :call functions -lf(::Val{:ref}, op, prevop, args; kw...) = "$(value(op))\\left[$(join(args, ", "))\\right]" -lf(::Val{:latexifymerge}, op, prevop, args; kw...) = string(value(op)) * join(args, "") +lf(::Val{:ref}, op, prevop, args; kw...) = "$(value(op))\\left[$(join(lf.(args, op), ", "))\\right]" +lf(::Val{:latexifymerge}, op, prevop, args; kw...) = string(value(op)) * join(lf.(args, op), "") + lf(head::Symbol, op::Symbol, prevop::Symbol, args) = lf(Val{head}(), Val{op}(), Val{prevop}(), args) lf(head::Symbol, op::Symbol, prevop, args) = lf(Val{head}(), Val{op}(), prevop, args) -lf(ex, prevop) = lf(head(ex), op(ex), prevop, args(ex)) +lf(ex, prevop=nothing) = nested(ex) ? lf(head(ex), op(ex), prevop, args(ex)) : string(ex) + + + -dive(ex, prevop=nothing) = dive(Val{nested(ex)}(), ex, prevop) -dive(::Val{false}, ex, prevop) = string(ex) -dive(::Val{true}, ex, prevop) = lf(head(ex), op(ex), prevop, dive.(args(ex), op(ex))) From e6238f3f605e5d3eb8954b4e3833f50aae0fa161 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sat, 30 Jan 2021 11:59:00 +0100 Subject: [PATCH 03/40] Add trig and assignment prototypes. --- refactor_prototype.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/refactor_prototype.jl b/refactor_prototype.jl index 76267b09..c0e4978b 100644 --- a/refactor_prototype.jl +++ b/refactor_prototype.jl @@ -32,9 +32,13 @@ end ### Fallback method for functions of type f(x...) lf(op, ::Any, args) = "$(value(op))\\left($(join(lf.(args, op), ", "))\\right)" -lf(op::ValUnion(Latexify.trigonometric_functions), ::Any, args) = "\\$(value(func))\\left($(join(lf.(args, op), ", "))\\right)" -# lf(func::Val{:sin}, ::Any, args) = "$(value(func))\\left($(join(args, ", "))\\right)" +function lf(op::ValUnion(Latexify.trigonometric_functions), ::Any, args) + fstr = get(Latexify.function2latex, value(op), "\\$(value(op))") + return "$fstr\\left($(join(lf.(args, op), ", "))\\right)" +end + +lf(::Val{:(=)}, op, prevop, args) = "$(lf(value(op), Val{:NoSurround}())) = $(lf(args[1], Val{:NoSurround}()))" function lf(op::Val{:+}, ::ValUnion(:*, :^), args) surround(join(lf.(args, op), " $(value(op)) ")) From 24c74402c5aedfa2f05749fae32bb3c67222449c Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sat, 30 Jan 2021 13:34:26 +0100 Subject: [PATCH 04/40] Rearrange refactor prototype. --- refactor_prototype.jl | 62 +++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/refactor_prototype.jl b/refactor_prototype.jl index c0e4978b..d6b0fd84 100644 --- a/refactor_prototype.jl +++ b/refactor_prototype.jl @@ -1,36 +1,39 @@ using Latexify -nested(ex::Expr) = true -args(ex::Expr) = ex.args[2:end] -op(ex::Expr) = ex.args[1] -head(ex::Expr) = ex.head - -arguments(ex::Expr) = ex.args[2:end] -operation(ex::Expr) = ex.args[1] -head(ex::Expr) = ex.head +nested(::Any) = false arguments(::Any) = nothing operation(::Any) = nothing head(::Any) = nothing +nested(::Expr) = true +arguments(ex::Expr) = ex.args[2:end] +operation(ex::Expr) = ex.args[1] +head(ex::Expr) = ex.head -nested(::Any) = false - - +### Ninja functions value(::Val{T}) where T = T isiterable(x) = hasmethod(iterate, (typeof(x),)) ValUnion(x) = isiterable(x) ? Union{typeof.(Val.(x))...} : Val{x} ValUnion(a, b, c...) = ValUnion((a, b, c...)) - +### Overloadable formatting functions surround(x) = "\\left( $x \\right)" -function strip_surround(x) - m = match(r"^\\left\( (.*) \\right\)$", x) - return isnothing(m) ? x : m.captures[1] -end +#### Automatic argument conversion for convenience + +# Reached the end of the tree? +lf(ex, prevop=nothing) = nested(ex) ? lf(head(ex), operation(ex), prevop, arguments(ex)) : string(ex) +# Let three-argument with omitted head imply that head == :call. +# This reduces verbosity of a bunch of calls. +lf(::Val{:call}, op, prevop, args) = lf(op, prevop, args) -### Fallback method for functions of type f(x...) +lf(head::Symbol, op::Symbol, prevop::Symbol, args) = lf(Val{head}(), Val{op}(), Val{prevop}(), args) +# Allows prevopts like `nothing` or just passing through `Val`'s +lf(head::Symbol, op::Symbol, prevop, args) = lf(Val{head}(), Val{op}(), prevop, args) + +#### :call functions +# Fallback method for functions of type f(x...) lf(op, ::Any, args) = "$(value(op))\\left($(join(lf.(args, op), ", "))\\right)" function lf(op::ValUnion(Latexify.trigonometric_functions), ::Any, args) @@ -38,23 +41,19 @@ function lf(op::ValUnion(Latexify.trigonometric_functions), ::Any, args) return "$fstr\\left($(join(lf.(args, op), ", "))\\right)" end -lf(::Val{:(=)}, op, prevop, args) = "$(lf(value(op), Val{:NoSurround}())) = $(lf(args[1], Val{:NoSurround}()))" - +lf(op::Val{:+}, prevop, args) = join(lf.(args, op), " + ") function lf(op::Val{:+}, ::ValUnion(:*, :^), args) surround(join(lf.(args, op), " $(value(op)) ")) end -lf(op::Val{:+}, prevop, args) = join(lf.(args, op), " + ") - -# ### router functions -lf(::Val{:call}, op, prevop, args) = lf(op, prevop, args) - -# ### :call functions -lf(op::Val{:*}, prevop, args; mul_symb=" \\cdot ", kw...) = - join(lf.(args, op), string(mul_symb)) lf(op::Val{:-}, prevop, args; kw...) = length(args) == 1 ? "- $(lf(args[1], op))" : join(lf.(args, op), " - ") + +lf(op::Val{:*}, prevop, args; mul_symb=" \\cdot ", kw...) = + join(lf.(args, op), string(mul_symb)) +lf(op::Val{:/}, prevop, args; kw...) = + "\\frac{$(lf(args[1], op))}{$(lf(args[2], op))}" function lf(op::Val{:^}, ::Any, args; kw...) if operation(args[1]) in Latexify.trigonometric_functions @@ -66,22 +65,15 @@ function lf(op::Val{:^}, ::Any, args; kw...) end end -lf(op::Val{:/}, prevop, args; kw...) = - "\\frac{$(lf(args[1], op))}{$(lf(args[2], op))}" lf(op::Val{:revealargs}, prevop, args; kw...) = args - # ### non :call functions lf(::Val{:ref}, op, prevop, args; kw...) = "$(value(op))\\left[$(join(lf.(args, op), ", "))\\right]" lf(::Val{:latexifymerge}, op, prevop, args; kw...) = string(value(op)) * join(lf.(args, op), "") +lf(::Val{:(=)}, op, prevop, args) = "$(lf(value(op), Val{:NoSurround}())) = $(lf(args[1], Val{:NoSurround}()))" -lf(head::Symbol, op::Symbol, prevop::Symbol, args) = lf(Val{head}(), Val{op}(), Val{prevop}(), args) -lf(head::Symbol, op::Symbol, prevop, args) = lf(Val{head}(), Val{op}(), prevop, args) -lf(ex, prevop=nothing) = nested(ex) ? lf(head(ex), op(ex), prevop, args(ex)) : string(ex) - - From 168fd2ea67357dcafda9c9fb037f42722f8712d0 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sun, 31 Jan 2021 11:16:50 +0100 Subject: [PATCH 05/40] Add meta customisation to refactor prototype. --- refactor_prototype.jl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/refactor_prototype.jl b/refactor_prototype.jl index d6b0fd84..f41ec6cd 100644 --- a/refactor_prototype.jl +++ b/refactor_prototype.jl @@ -1,4 +1,5 @@ using Latexify +using LaTeXStrings nested(::Any) = false arguments(::Any) = nothing @@ -65,15 +66,23 @@ function lf(op::Val{:^}, ::Any, args; kw...) end end -lf(op::Val{:revealargs}, prevop, args; kw...) = args - # ### non :call functions lf(::Val{:ref}, op, prevop, args; kw...) = "$(value(op))\\left[$(join(lf.(args, op), ", "))\\right]" -lf(::Val{:latexifymerge}, op, prevop, args; kw...) = string(value(op)) * join(lf.(args, op), "") lf(::Val{:(=)}, op, prevop, args) = "$(lf(value(op), Val{:NoSurround}())) = $(lf(args[1], Val{:NoSurround}()))" +##### Latexify special commands +lf(::Val{:showargs}, prevop, args; kw...) = string(args) +lf(::Val{:showprevop}, prevop, args; kw...) = string(prevop) +lf(::Val{:textcolor}, prevop, args) = "\\textcolor{$(args[2])}{$(lf(args[1], prevop))}" +lf(::Val{:mathrm}, prevop, args) = "\\mathrm{$(lf(args[1], prevop))}" +lf(::Val{:merge}, prevop, args; kw...) = join(lf.(args, prevop), "") + +function latexdive(x) + str = lf(x) + return LaTeXString(str) +end From f7565255380a805a1515370a72013a80ca717549 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 16 Feb 2021 15:52:17 +0100 Subject: [PATCH 06/40] Update refactor protoypte. --- refactor_prototype.jl | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/refactor_prototype.jl b/refactor_prototype.jl index f41ec6cd..a255b975 100644 --- a/refactor_prototype.jl +++ b/refactor_prototype.jl @@ -17,6 +17,8 @@ isiterable(x) = hasmethod(iterate, (typeof(x),)) ValUnion(x) = isiterable(x) ? Union{typeof.(Val.(x))...} : Val{x} ValUnion(a, b, c...) = ValUnion((a, b, c...)) +joinsafe(x, y) = isiterable(x) ? join(x, y) : string(x) + ### Overloadable formatting functions surround(x) = "\\left( $x \\right)" @@ -47,16 +49,16 @@ function lf(op::Val{:+}, ::ValUnion(:*, :^), args) surround(join(lf.(args, op), " $(value(op)) ")) end -lf(op::Val{:-}, prevop, args; kw...) = +lf(op::ValUnion(:-, :.-), prevop, args; kw...) = length(args) == 1 ? "- $(lf(args[1], op))" : join(lf.(args, op), " - ") -lf(op::Val{:*}, prevop, args; mul_symb=" \\cdot ", kw...) = +lf(op::ValUnion(:*, :.*), prevop, args; mul_symb=" \\cdot ", kw...) = join(lf.(args, op), string(mul_symb)) -lf(op::Val{:/}, prevop, args; kw...) = +lf(op::ValUnion(:/, :./), prevop, args; kw...) = "\\frac{$(lf(args[1], op))}{$(lf(args[2], op))}" -function lf(op::Val{:^}, ::Any, args; kw...) +function lf(op::ValUnion(:^, :.^), ::Any, args; kw...) if operation(args[1]) in Latexify.trigonometric_functions fsym = args[1] fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") @@ -66,6 +68,8 @@ function lf(op::Val{:^}, ::Any, args; kw...) end end +lf(op::ValUnion(:±, :.±), prevop, args) = "$(args[1]) \\pm $(lf(args[2], op))" + # ### non :call functions lf(::Val{:ref}, op, prevop, args; kw...) = "$(value(op))\\left[$(join(lf.(args, op), ", "))\\right]" lf(::Val{:(=)}, op, prevop, args) = "$(lf(value(op), Val{:NoSurround}())) = $(lf(args[1], Val{:NoSurround}()))" @@ -73,16 +77,22 @@ lf(::Val{:(=)}, op, prevop, args) = "$(lf(value(op), Val{:NoSurround}())) = $(lf ##### Latexify special commands lf(::Val{:showargs}, prevop, args; kw...) = string(args) lf(::Val{:showprevop}, prevop, args; kw...) = string(prevop) -lf(::Val{:textcolor}, prevop, args) = "\\textcolor{$(args[2])}{$(lf(args[1], prevop))}" +function lf(::Val{:textcolor}, prevop, args) + if length(args) == 2 + return "\\textcolor{$(head(args[1]) == :tuple ? join(args[1].args, ",") : args[1])}{$(lf(args[2], prevop))}" + elseif length(args) == 3 + return "\\textcolor[$(joinsafe(args[1], ","))]{$(head(args[2]) == :tuple ? join(args[2].args, ",") : args[2])}{$(lf(args[3], prevop))}" + else + error(ArgumentError("Latexify's textcolor takes two or three arguments.")) + end +end lf(::Val{:mathrm}, prevop, args) = "\\mathrm{$(lf(args[1], prevop))}" lf(::Val{:merge}, prevop, args; kw...) = join(lf.(args, prevop), "") - function latexdive(x) str = lf(x) return LaTeXString(str) end - From 6f45c4f6705a3970cee971c55799ba5d19e963ae Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 16 Feb 2021 15:52:55 +0100 Subject: [PATCH 07/40] Add function-list refactor prototype. --- refactor_prototype_2.jl | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 refactor_prototype_2.jl diff --git a/refactor_prototype_2.jl b/refactor_prototype_2.jl new file mode 100644 index 00000000..ce8841ce --- /dev/null +++ b/refactor_prototype_2.jl @@ -0,0 +1,61 @@ + +const MATCHING_FUNCTIONS = Function[] + +add_matcher(f) = push!(MATCHING_FUNCTIONS, f) + +_check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op + +# User would write: +# text_color_latexify(e) = "\\textcolor{$(args[1])}{$(latexify(args[2]))}" +# add_call_matcher(text_color_latexify, :textcolor) +function add_call_matcher(f::Function, op::Symbol) + push!(MATCHING_FUNCTIONS, (e, p) -> _check_call_match(e, op) ? f(e) : nothing) +end + + +nested(::Any) = false +arguments(::Any) = nothing +operation(::Any) = nothing +head(::Any) = nothing + +nested(::Expr) = true +arguments(ex::Expr) = ex.args[2:end] +operation(ex::Expr) = ex.args[1] +head(ex::Expr) = ex.head + +func(x, prevop) = string(x) +func(x::Expr, prevop::Symbol) = func(x, Val{prevop}()) +function func(e::Expr, prevop=Val(:_nothing)) + for f in MATCHING_FUNCTIONS + if f(e, prevop) !== nothing + return f(e, prevop) + break + end + end + return "Caught nothing!!" +end + + +### Overloadable formatting functions +surround(x) = "\\left( $x \\right)" + +# match_equals(e) = (e isa Expr && e.head === :=) ? + # "$(latexify(value(lhs_val))) = $(latexify(first(rhs_array)))" : + # nothing + + + +match_addition(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :+) ? join(func.(e.args[2:end], e.args[1]), " + ") : nothing +match_addition(e, prevop::Val{:*}) = (e isa Expr && e.head==:call && e.args[1] == :+) ? surround(join(func.(e.args[2:end], e.args[1]), " + ")) : nothing + +match_division(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :/) ? "\\frac{$(func(e.args[2], e.args[1]))}{$(func(e.args[3], e.args[1]))}" : nothing + +match_mul(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :*) ? join(func.(e.args[2:end], Val{:*}()), " \\cdot ") : nothing + +struct Operation{T} + head::Symbol + op::Symbol + args::T + prevop::Union{Symbol, Nothing} +end +Operation(ex::Expr, prevop) = Operation(ex.head, ex.args[1], ex.args[2:end], prevop) \ No newline at end of file From c20820b3819c0babc27db9143d71e41ce4c46d3a Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 23 Feb 2021 14:18:41 +0100 Subject: [PATCH 08/40] Revise refactor prototype 2. --- refactor_prototype_2.jl | 80 ++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/refactor_prototype_2.jl b/refactor_prototype_2.jl index ce8841ce..a75b7f8b 100644 --- a/refactor_prototype_2.jl +++ b/refactor_prototype_2.jl @@ -1,15 +1,15 @@ -const MATCHING_FUNCTIONS = Function[] add_matcher(f) = push!(MATCHING_FUNCTIONS, f) _check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op +_check_call_match(e, op::AbstractArray) = e isa Expr && e.head === :call && e.args[1] ∈ op # User would write: # text_color_latexify(e) = "\\textcolor{$(args[1])}{$(latexify(args[2]))}" # add_call_matcher(text_color_latexify, :textcolor) -function add_call_matcher(f::Function, op::Symbol) - push!(MATCHING_FUNCTIONS, (e, p) -> _check_call_match(e, op) ? f(e) : nothing) +function add_call_matcher(op, f::Function) + push!(MATCHING_FUNCTIONS, (e, p) -> _check_call_match(e, op) ? f(e, p) : nothing) end @@ -18,17 +18,21 @@ arguments(::Any) = nothing operation(::Any) = nothing head(::Any) = nothing +unpack(x) = (head(x), operation(x), arguments(x)) + nested(::Expr) = true arguments(ex::Expr) = ex.args[2:end] operation(ex::Expr) = ex.args[1] head(ex::Expr) = ex.head -func(x, prevop) = string(x) -func(x::Expr, prevop::Symbol) = func(x, Val{prevop}()) -function func(e::Expr, prevop=Val(:_nothing)) - for f in MATCHING_FUNCTIONS - if f(e, prevop) !== nothing - return f(e, prevop) +# func(x, prevop) = string(x) +# func(x::Expr, prevop::Symbol) = func(x, Val{prevop}()) +function func(e, prevop=Val(:_nothing); kwargs...) + nested(e) || return Latexify.latexraw(e; kwargs...) + for f in MATCHING_FUNCTIONS[end:-1:1] + call_result = f(e, prevop) + if !isnothing(call_result) + return call_result break end end @@ -43,19 +47,47 @@ surround(x) = "\\left( $x \\right)" # "$(latexify(value(lhs_val))) = $(latexify(first(rhs_array)))" : # nothing - - -match_addition(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :+) ? join(func.(e.args[2:end], e.args[1]), " + ") : nothing -match_addition(e, prevop::Val{:*}) = (e isa Expr && e.head==:call && e.args[1] == :+) ? surround(join(func.(e.args[2:end], e.args[1]), " + ")) : nothing - -match_division(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :/) ? "\\frac{$(func(e.args[2], e.args[1]))}{$(func(e.args[3], e.args[1]))}" : nothing - -match_mul(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :*) ? join(func.(e.args[2:end], Val{:*}()), " \\cdot ") : nothing - -struct Operation{T} - head::Symbol - op::Symbol - args::T - prevop::Union{Symbol, Nothing} +function default_matcher() +return [ + function call(expr, prevop) + h, op, args = unpack(expr) + if h == :call + string(get(Latexify.function2latex, op, op)) * "\\left( " * join(func.(args, op), ",") * " \\right)" + end + end, + function division(expr, prevop) + h, op, args = unpack(expr) + if h == :call && op == :/ + "\\frac{$(func(args[1], op))}{$(func(args[2], op))}" + end + end, + function multiplication(expr, prevop) + h, op, args = unpack(expr) + if h == :call && op == :* + join(func.(args, op), " \\cdot ") + end + end, + function addition(expr, prevop) + h, op, args = unpack(expr) + if h == :call && op == :+ + str = join(func.(args, op), " + ") + prevop ∈ [:*, :^] && (str = surround(str)) + return str + end + end, + function pow(expr, prevop) + h, op, args = unpack(expr) + if h == :call && op == :^ + if operation(args[1]) in Latexify.trigonometric_functions + fsym = operation(args[1]) + fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") + "$fstring^{$(func(args[2], op))}\\left( $(join(func.(arguments(args[1]), operation(args[1])), ", ")) \\right)" + else + "$(func(args[1], op))^{$(func(args[2], Val{:NoSurround}()))}" + end + end + end +] end -Operation(ex::Expr, prevop) = Operation(ex.head, ex.args[1], ex.args[2:end], prevop) \ No newline at end of file + +const MATCHING_FUNCTIONS = default_matcher() \ No newline at end of file From 54dde4f9bd27995e0f18ddf1c8468c58d928aa8b Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sun, 28 Feb 2021 15:08:41 +0100 Subject: [PATCH 09/40] Move refactor prototype into src --- refactor_prototype_2.jl | 93 ----------------------------------------- refactor_prototype_3.jl | 77 ++++++++++++++++++++++++++++++++++ src/Latexify.jl | 2 + 3 files changed, 79 insertions(+), 93 deletions(-) delete mode 100644 refactor_prototype_2.jl create mode 100644 refactor_prototype_3.jl diff --git a/refactor_prototype_2.jl b/refactor_prototype_2.jl deleted file mode 100644 index a75b7f8b..00000000 --- a/refactor_prototype_2.jl +++ /dev/null @@ -1,93 +0,0 @@ - - -add_matcher(f) = push!(MATCHING_FUNCTIONS, f) - -_check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op -_check_call_match(e, op::AbstractArray) = e isa Expr && e.head === :call && e.args[1] ∈ op - -# User would write: -# text_color_latexify(e) = "\\textcolor{$(args[1])}{$(latexify(args[2]))}" -# add_call_matcher(text_color_latexify, :textcolor) -function add_call_matcher(op, f::Function) - push!(MATCHING_FUNCTIONS, (e, p) -> _check_call_match(e, op) ? f(e, p) : nothing) -end - - -nested(::Any) = false -arguments(::Any) = nothing -operation(::Any) = nothing -head(::Any) = nothing - -unpack(x) = (head(x), operation(x), arguments(x)) - -nested(::Expr) = true -arguments(ex::Expr) = ex.args[2:end] -operation(ex::Expr) = ex.args[1] -head(ex::Expr) = ex.head - -# func(x, prevop) = string(x) -# func(x::Expr, prevop::Symbol) = func(x, Val{prevop}()) -function func(e, prevop=Val(:_nothing); kwargs...) - nested(e) || return Latexify.latexraw(e; kwargs...) - for f in MATCHING_FUNCTIONS[end:-1:1] - call_result = f(e, prevop) - if !isnothing(call_result) - return call_result - break - end - end - return "Caught nothing!!" -end - - -### Overloadable formatting functions -surround(x) = "\\left( $x \\right)" - -# match_equals(e) = (e isa Expr && e.head === :=) ? - # "$(latexify(value(lhs_val))) = $(latexify(first(rhs_array)))" : - # nothing - -function default_matcher() -return [ - function call(expr, prevop) - h, op, args = unpack(expr) - if h == :call - string(get(Latexify.function2latex, op, op)) * "\\left( " * join(func.(args, op), ",") * " \\right)" - end - end, - function division(expr, prevop) - h, op, args = unpack(expr) - if h == :call && op == :/ - "\\frac{$(func(args[1], op))}{$(func(args[2], op))}" - end - end, - function multiplication(expr, prevop) - h, op, args = unpack(expr) - if h == :call && op == :* - join(func.(args, op), " \\cdot ") - end - end, - function addition(expr, prevop) - h, op, args = unpack(expr) - if h == :call && op == :+ - str = join(func.(args, op), " + ") - prevop ∈ [:*, :^] && (str = surround(str)) - return str - end - end, - function pow(expr, prevop) - h, op, args = unpack(expr) - if h == :call && op == :^ - if operation(args[1]) in Latexify.trigonometric_functions - fsym = operation(args[1]) - fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") - "$fstring^{$(func(args[2], op))}\\left( $(join(func.(arguments(args[1]), operation(args[1])), ", ")) \\right)" - else - "$(func(args[1], op))^{$(func(args[2], Val{:NoSurround}()))}" - end - end - end -] -end - -const MATCHING_FUNCTIONS = default_matcher() \ No newline at end of file diff --git a/refactor_prototype_3.jl b/refactor_prototype_3.jl new file mode 100644 index 00000000..d20a6768 --- /dev/null +++ b/refactor_prototype_3.jl @@ -0,0 +1,77 @@ +const MATCHING_FUNCTIONS = Function[] + +add_matcher(f) = push!(MATCHING_FUNCTIONS, f) + +_check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op + +# User would write: +# text_color_latexify(e) = "\\textcolor{$(args[1])}{$(latexify(args[2]))}" +# add_call_matcher(text_color_latexify, :textcolor) +function add_call_matcher(op::Symbol, f::Function) + push!(MATCHING_FUNCTIONS, (e, p) -> _check_call_match(e, op) ? f(e, p) : nothing) +end + + +nested(::Any) = false +arguments(::Any) = nothing +operation(::Any) = nothing +head(::Any) = nothing + +nested(::Expr) = true +arguments(ex::Expr) = ex.args[2:end] +operation(ex::Expr) = ex.args[1] +head(ex::Expr) = ex.head + + +surround(x) = "\\left( $x \\right)" + +function dive(x, prevop=nothing) + args = map(arg -> nested(arg) ? dive(arg, operation(x)) : arg, arguments(x)) +# latexterminal(head(x), operation(x), args, prevop) + for f in MATCHING_FUNCTIONS[end:-1:1] + if f(head(x), operation(x), args, prevop) !== nothing + return f(head(x), operation(x), args, prevop) + break + end + end + return "Caught nothing!!" +end + + +if false +func(x, prevop) = string(x) +# func(x::Expr, prevop::Symbol) = func(x, Val{prevop}()) +function func(e::Expr, prevop=Val(:_nothing)) + for f in MATCHING_FUNCTIONS[end:-1:1] + if f(e, prevop) !== nothing + return f(e, prevop) + break + end + end + return "Caught nothing!!" +end + + +### Overloadable formatting functions + +# match_equals(e) = (e isa Expr && e.head === :=) ? + # "$(latexify(value(lhs_val))) = $(latexify(first(rhs_array)))" : + # nothing + + + +match_addition(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :+) ? join(func.(e.args[2:end], e.args[1]), " + ") : nothing +match_addition(e, prevop::Val{:*}) = (e isa Expr && e.head==:call && e.args[1] == :+) ? surround(join(func.(e.args[2:end], e.args[1]), " + ")) : nothing + +match_division(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :/) ? "\\frac{$(func(e.args[2], e.args[1]))}{$(func(e.args[3], e.args[1]))}" : nothing + +match_mul(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :*) ? join(func.(e.args[2:end], Val{:*}()), " \\cdot ") : nothing + +struct Operation{T} + head::Symbol + op::Symbol + args::T + prevop::Union{Symbol, Nothing} +end +Operation(ex::Expr, prevop) = Operation(ex.head, ex.args[1], ex.args[2:end], prevop) +end \ No newline at end of file diff --git a/src/Latexify.jl b/src/Latexify.jl index 69de2457..8b6a1c5e 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -49,6 +49,8 @@ include("utils.jl") include("numberformatters.jl") include("latexify_function.jl") +include("refactor_prototype_2.jl") +export decend, unpack, head, operation, arguments, nested, default_matcher, LatexifyOperation ### Add support for additional packages without adding them as dependencies. function __init__() From ae1a6fa8fafe67eadf9428787566d2b267967202 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sun, 28 Feb 2021 15:09:16 +0100 Subject: [PATCH 10/40] Add renrer `\usepackage`. --- src/utils.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index 6e414d6e..30601dfb 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -16,7 +16,8 @@ function _writetex(s::LaTeXString; name=tempname(), command="\\Large") \\documentclass[varwidth=100cm]{standalone} \\usepackage{amssymb} \\usepackage{amsmath} - $(occursin("\\ce{", s) ? "\\usepackage{mhchem}" : "") + $(occursin("\\ce{", s) ? "\\usepackage[version=3]{mhchem}" : "") + $(occursin("\\textcolor{", s) ? "\\usepackage{xcolor}" : "") \\begin{document} { $command From a64c3516cc385d4710b259b890ac94657c6f4e60 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 6 Apr 2021 14:50:26 +0200 Subject: [PATCH 11/40] Integrate and update refactor prototype. --- src/latexraw.jl | 28 +----- src/refactor_prototype_2.jl | 174 ++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 src/refactor_prototype_2.jl diff --git a/src/latexraw.jl b/src/latexraw.jl index 58e38961..8045c527 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -57,32 +57,8 @@ latexraw(symExpr) """ latexraw(args...; kwargs...) = latexify(args...; kwargs..., env=:raw) -function _latexraw(inputex::Expr; convert_unicode=true, kwargs...) - ## Pass all arrays or matrices in the expr to latexarray - inputex = postwalk(x -> x isa Expr && x.head in [:hcat, :vcat, :vect, :typed_vcat, :typed_hcat] ? - latexarray(expr_to_array(x); kwargs...) - : x, - inputex) - - recurseexp!(lstr::LaTeXString) = lstr.s - function recurseexp!(ex) - prevOp = Vector{Symbol}(undef, length(ex.args)) - fill!(prevOp, :none) - for i in 1:length(ex.args) - if isa(ex.args[i], Expr) - length(ex.args[i].args) > 1 && ex.args[i].args[1] isa Symbol && (prevOp[i] = ex.args[i].args[1]) - ex.args[i] = recurseexp!(ex.args[i]) - elseif ex.args[i] isa AbstractArray - ex.args[i] = latexraw(ex.args[i]; kwargs...) - end - end - return latexoperation(ex, prevOp; convert_unicode=convert_unicode, kwargs...) - end - ex = deepcopy(inputex) - str = recurseexp!(ex) - convert_unicode && (str = unicode2latex(str)) - return LaTeXString(str) -end +_latexraw(ex::Expr; kwargs...) = _latextree(ex; kwargs...) +_latexraw(ex; kwargs...) = nested(ex) ? _latextree(ex; kwargs...) : throw(ArgumentError("Unsupported type $(typeof(ex)) to Latexify._latexraw")) function _latexraw(args...; kwargs...) diff --git a/src/refactor_prototype_2.jl b/src/refactor_prototype_2.jl new file mode 100644 index 00000000..a1e469ea --- /dev/null +++ b/src/refactor_prototype_2.jl @@ -0,0 +1,174 @@ + + +add_matcher(f) = push!(MATCHING_FUNCTIONS, f) + +_check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op +_check_call_match(e, op::AbstractArray) = e isa Expr && e.head === :call && e.args[1] ∈ op + +# User would write: +# text_color_latexify(e) = "\\textcolor{$(args[1])}{$(latexify(args[2]))}" +# add_call_matcher(text_color_latexify, :textcolor) +function add_call_matcher(op, f::Function) + push!(MATCHING_FUNCTIONS, (expr, prevop, config) -> _check_call_match(expr, op) ? f(expr, prevop, config) : nothing) +end + + +nested(::Any) = false +arguments(::Any) = nothing +operation(::Any) = nothing +head(::Any) = nothing + +unpack(x) = (head(x), operation(x), arguments(x)) + +nested(::Expr) = true +arguments(ex::Expr) = ex.args[2:end] +operation(ex::Expr) = ex.args[1] +head(ex::Expr) = ex.head + + +const DEFAULT_CONFIG = Dict{Symbol, Any}( + :mulsym => " \\cdot ", + :convert_unicode => true, + :strip_broadcast => true, +) +const CONFIG = Dict{Symbol, Any}() + +getconfig(key::Symbol) = CONFIG[key] + +function _latextree(expr; kwargs...) + empty!(CONFIG) + merge!(CONFIG, DEFAULT_CONFIG, kwargs) + str = decend(expr) + CONFIG[:convert_unicode] && (str = unicode2latex(str)) + return LaTeXString(str) +end +# decend(x, prevop) = string(x) +# decend(x::Expr, prevop::Symbol) = decend(x, Val{prevop}()) +function decend(e, prevop=Val(:_nothing))::String + if nested(e) + # nested(e) || return Latexify.latexraw(e; kwargs...) + for f in MATCHING_FUNCTIONS[end:-1:1] + call_result = f(e, prevop, CONFIG) + if !isnothing(call_result) + return call_result + break + end + end + throw(ArgumentError("No matching expression conversion function for $e")) + else + return _latexraw(e; CONFIG...) + end +end + + +### Overloadable formatting functions +surround(x) = "\\left( $x \\right)" + +# match_equals(e) = (e isa Expr && e.head === :=) ? + # "$(latexify(value(lhs_val))) = $(latexify(first(rhs_array)))" : + # nothing + +function default_matcher() +return [ +function report_bad_call(expr, prevop, config) + println("Unsupported input with \nexpr=$expr\nand prevop=$prevop\n") + return nothing +end, + (expr, prevop, config) -> begin + h, op, args = unpack(expr) + # if (expr isa LatexifyOperation || h == :LatexifyOperation) && op == :merge + if h == :call && op == :latexifymerge + join(decend.(args), "") + end + end, + function call(expr, prevop, config) + h, op, args = unpack(expr) + if h == :call + string(get(Latexify.function2latex, op, op)) * "\\left( " * join(decend.(args, op), ",") * " \\right)" + end + end, + function strip_broadcast_dot(expr, prevop, config) + h, op, args = unpack(expr) + if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') + return string(decend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) + end + end, + function plusminus(expr, prevop, config) + h, op, args = unpack(expr) + if h == :call && op == :± + return "$(args[1]) \\pm $(args[2])" + end + end, + function division(expr, prevop, config) + h, op, args = unpack(expr) + if h == :call && op == :/ + "\\frac{$(decend(args[1], op))}{$(decend(args[2], op))}" + end + end, + function multiplication(expr, prevop, config) + h, op, args = unpack(expr) + if h == :call && op == :* + join(decend.(args, op), "$(config[:mulsym])") + end + end, + function addition(expr, prevop, config) + h, op, args = unpack(expr) + if h == :call && op == :+ + str = join(decend.(args, op), " + ") + prevop ∈ [:*, :^] && (str = surround(str)) + return str + end + end, + function subtraction(expr, prevop, config) + h, op, args = unpack(expr) + if h == :call && op == :- + if length(args) == 1 + if operation(args[1]) == :- && length(arguments(args[1])) == 1 + return decend(arguments(args[1])[1], prevop) + elseif args[1] isa Number && sign(args[1]) == -1 + return _latexraw(-args[1]) + else + _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] + return "$op$_arg" + end + + elseif length(args) == 2 + if args[2] isa Number && sign(args[2]) == -1 + return "$(decend(args[1], :+)) + $(decend(-args[2], :+))" + end + if operation(args[2]) == :- && length(arguments(args[2])) == 1 + return "$(decend(args[1], :+)) + $(decend(arguments(args[2])[1], :+))" + end + str = join(decend.(args, op), " - ") + prevop ∈ [:*, :^] && (str = surround(str)) + return str + end + end + end, + function pow(expr, prevop, config) + h, op, args = unpack(expr) + if h == :call && op == :^ + if operation(args[1]) in Latexify.trigonometric_functions + fsym = operation(args[1]) + fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") + "$fstring^{$(decend(args[2], op))}\\left( $(join(decend.(arguments(args[1]), operation(args[1])), ", ")) \\right)" + else + "$(decend(args[1], op))^{$(decend(args[2], Val{:NoSurround}()))}" + end + end + end, + function equals(expr, prevop, config) + if expr.head == :(=) + return "$(decend(expr.args[1], expr.head)) = $(decend(expr.args[2], expr.head))" + end + end, + function l_funcs(ex, prevop, config) + if head(ex) == :call && startswith(string(operation(ex)), "l_") + l_func = string(operation(ex))[3:end] + return "\\$(l_func){$(join(decend.(arguments(ex), prevop), "}{"))}" + end + end, +] +end + +const MATCHING_FUNCTIONS = default_matcher() \ No newline at end of file From 4acc683d246a0334fd92951c5c70fdfddef33f09 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Wed, 7 Apr 2021 09:21:15 +0200 Subject: [PATCH 12/40] Stop recusing to top-level call. Breaks recipes. In this breaking change, I stop re-routing all calls to latexraw via latexify. This was previously done to enable recursive recipe application. I'm now developing a more performant alterantive. --- src/latexraw.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/latexraw.jl b/src/latexraw.jl index 8045c527..51fbe52f 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -55,7 +55,8 @@ latexraw(symExpr) "2 \\cdot x + x \\cdot y^{2}" ``` """ -latexraw(args...; kwargs...) = latexify(args...; kwargs..., env=:raw) +# latexraw(args...; kwargs...) = latexify(args...; kwargs..., env=:raw) +latexraw(args...; kwargs...) = _latexraw(args...; kwargs...) _latexraw(ex::Expr; kwargs...) = _latextree(ex; kwargs...) _latexraw(ex; kwargs...) = nested(ex) ? _latextree(ex; kwargs...) : throw(ArgumentError("Unsupported type $(typeof(ex)) to Latexify._latexraw")) @@ -77,7 +78,7 @@ _latexraw(str::LaTeXStrings.LaTeXString; kwargs...) = str function _latexraw(i::Number; fmt=PlainNumberFormatter(), kwargs...) try isinf(i) && return LaTeXString("\\infty") catch; end fmt isa String && (fmt = PrintfNumberFormatter(fmt)) - return fmt(i) + return LaTeXString(fmt(i)) end function _latexraw(i::Char; convert_unicode=true, kwargs...) From 137dcb86c73b506e96de0734ec5fadc8175da114 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Wed, 7 Apr 2021 09:21:47 +0200 Subject: [PATCH 13/40] Enable rendering via a kwargs. --- src/latexify_function.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/latexify_function.jl b/src/latexify_function.jl index 985c3f4e..59d821e6 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -12,6 +12,7 @@ function latexify(args...; kwargs...) COPY_TO_CLIPBOARD && clipboard(result) AUTO_DISPLAY && display(result) + get(kwargs, :render, false) && render(result) return result end From 473032a49748d7b5335fca644d24244adaf7280b Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Wed, 7 Apr 2021 09:23:04 +0200 Subject: [PATCH 14/40] Add non-nested types to `decend`. --- src/refactor_prototype_2.jl | 66 ++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/src/refactor_prototype_2.jl b/src/refactor_prototype_2.jl index a1e469ea..5894a060 100644 --- a/src/refactor_prototype_2.jl +++ b/src/refactor_prototype_2.jl @@ -45,7 +45,7 @@ end # decend(x, prevop) = string(x) # decend(x::Expr, prevop::Symbol) = decend(x, Val{prevop}()) function decend(e, prevop=Val(:_nothing))::String - if nested(e) + # if nested(e) # nested(e) || return Latexify.latexraw(e; kwargs...) for f in MATCHING_FUNCTIONS[end:-1:1] call_result = f(e, prevop, CONFIG) @@ -55,9 +55,9 @@ function decend(e, prevop=Val(:_nothing))::String end end throw(ArgumentError("No matching expression conversion function for $e")) - else - return _latexraw(e; CONFIG...) - end + # else + # return _latexraw(e; CONFIG...) + # end end @@ -68,12 +68,41 @@ surround(x) = "\\left( $x \\right)" # "$(latexify(value(lhs_val))) = $(latexify(first(rhs_array)))" : # nothing -function default_matcher() -return [ -function report_bad_call(expr, prevop, config) - println("Unsupported input with \nexpr=$expr\nand prevop=$prevop\n") - return nothing -end, +# function default_matcher() +# return +const MATCHING_FUNCTIONS = [ + function report_bad_call(expr, prevop, config) + println(""" + Unsupported input with + expr=$expr + prevop=$prevop + and config=$config + """) + return nothing + end, + function _block(x, args...) + if head(x) == :block && length(filter(x->!(x isa LineNumberNode), arguments(x))) == 1 + return decend(arguments(x)[end]) + end + end, + function number(x, args...) + x isa Number ? string(x) : nothing + end, + function symbol(x, args...) + x isa Symbol ? string(x) : nothing + end, + function array(x, args...) + x isa AbstractArray ? _latexarray(x) : nothing + end, + function tuple(x, args...) + x isa Tuple ? _latexarray(x) : nothing + end, + function vect_exp(x, args...) + head(x)==:vect ? _latexarray(vcat(operation(x), arguments(x))) : nothing + end, + function hcat_exp(x, args...) + head(x)==:hcat ? _latexarray(permutedims(vcat(operation(x), arguments(x)))) : nothing + end, (expr, prevop, config) -> begin h, op, args = unpack(expr) # if (expr isa LatexifyOperation || h == :LatexifyOperation) && op == :merge @@ -96,7 +125,7 @@ end, function plusminus(expr, prevop, config) h, op, args = unpack(expr) if h == :call && op == :± - return "$(args[1]) \\pm $(args[2])" + return "$(decend(args[1], op)) \\pm $(decend(args[2], op))" end end, function division(expr, prevop, config) @@ -115,18 +144,22 @@ end, h, op, args = unpack(expr) if h == :call && op == :+ str = join(decend.(args, op), " + ") + str = replace(str, "+ -"=>"-") prevop ∈ [:*, :^] && (str = surround(str)) return str end end, function subtraction(expr, prevop, config) + # this one is so gnarly because it tries to fix stuff like - - or -(-(x-y)) + # -(x) is also a bit different to -(x, y) which does not make things simpler h, op, args = unpack(expr) if h == :call && op == :- if length(args) == 1 if operation(args[1]) == :- && length(arguments(args[1])) == 1 return decend(arguments(args[1])[1], prevop) elseif args[1] isa Number && sign(args[1]) == -1 - return _latexraw(-args[1]) + # return _latexraw(-args[1]; config...) + return decend(-args[1]; config...) else _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] return "$op$_arg" @@ -139,6 +172,9 @@ end, if operation(args[2]) == :- && length(arguments(args[2])) == 1 return "$(decend(args[1], :+)) + $(decend(arguments(args[2])[1], :+))" end + if operation(args[2]) ∈ [:-, :.-, :+, :.+] + return "$(decend(args[1], op)) - $(surround(decend(args[2], op)))" + end str = join(decend.(args, op), " - ") prevop ∈ [:*, :^] && (str = surround(str)) return str @@ -158,7 +194,7 @@ end, end end, function equals(expr, prevop, config) - if expr.head == :(=) + if head(expr) == :(=) return "$(decend(expr.args[1], expr.head)) = $(decend(expr.args[2], expr.head))" end end, @@ -169,6 +205,6 @@ end, end end, ] -end +# end -const MATCHING_FUNCTIONS = default_matcher() \ No newline at end of file +# const MATCHING_FUNCTIONS = default_matcher() \ No newline at end of file From ba94fc92298287dbe6c38298d5554807e80b1973 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 9 Apr 2021 14:40:48 +0200 Subject: [PATCH 15/40] Implement all operations in refactor. --- src/refactor_prototype_2.jl | 244 +++++++++++++++++++++++++++++++++--- 1 file changed, 228 insertions(+), 16 deletions(-) diff --git a/src/refactor_prototype_2.jl b/src/refactor_prototype_2.jl index 5894a060..3ef32b94 100644 --- a/src/refactor_prototype_2.jl +++ b/src/refactor_prototype_2.jl @@ -30,11 +30,15 @@ const DEFAULT_CONFIG = Dict{Symbol, Any}( :mulsym => " \\cdot ", :convert_unicode => true, :strip_broadcast => true, + :fmt => identity, + :index => :bracket, + :ifstr => "\\text{if }", + :elseifstr => "\\text{elseif }", + :elsestr => "\\text{otherwise}", ) const CONFIG = Dict{Symbol, Any}() getconfig(key::Symbol) = CONFIG[key] - function _latextree(expr; kwargs...) empty!(CONFIG) merge!(CONFIG, DEFAULT_CONFIG, kwargs) @@ -70,6 +74,22 @@ surround(x) = "\\left( $x \\right)" # function default_matcher() # return + +const comparison_operators = Dict( + :< => "<", + :.< => "<", + :> => ">", + :.> => ">", + Symbol("==") => "=", + Symbol(".==") => "=", + :<= => "\\leq", + :.<= => "\\leq", + :>= => "\\geq", + :.>= => "\\geq", + :!= => "\\neq", + :.!= => "\\neq", + ) + const MATCHING_FUNCTIONS = [ function report_bad_call(expr, prevop, config) println(""" @@ -80,16 +100,150 @@ const MATCHING_FUNCTIONS = [ """) return nothing end, + function call(expr, prevop, config) + if expr isa Expr && head(expr) == :call + h, op, args = unpack(expr) + if op isa Symbol + return string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) * "\\left( " * join(decend.(args, op), ", ") * " \\right)" + else + return decend(op) * "\\left( " * join(decend.(args), ",") * " \\right)" + end + end + end, + function _sqrt(ex, prevop, config) + operation(ex) == :sqrt ? "\\$(operation(ex)){$(arguments(ex)[1])}" : nothing + end, + function _abs(ex, prevop, config) + operation(ex) == :abs ? "\\left\\|$(arguments(ex)[1])\\right\\|" : nothing + end, + function _single_comparison(ex, args...) + if operation(ex) ∈ keys(comparison_operators) && length(arguments(ex)) == 2 + str = "$(arguments(ex)[1]) $(comparison_operators[operation(ex)]) $(arguments(ex)[2])" + str = "\\left( $str \\right)" + return str + end + end, + function _if(ex, prevop, config) + if ex isa Expr && head(ex) == :if + str = build_if_else_body( + ex.args, + getconfig(:ifstr), + getconfig(:elseifstr), + getconfig(:elsestr) + ) + return """ + \\begin{cases} + $str + \\end{cases}""" + end + end, + function _elseif(ex, prevop, config) + if ex isa Expr && head(ex) == :elseif + str = build_if_else_body( + ex.args, + getconfig(:elseifstr), + getconfig(:elseifstr), + getconfig(:elsestr) + ) + end + end, + function _oneline_function(ex, prevop, config) + if head(ex) == :function && length(arguments(ex)) == 1 + return "$(decend(operation(ex), head(ex))) = $(decend(arguments(ex)[1], head(ex)))" + end + end, + function _return(ex, prevop, config) + head(ex) == :return && length(arguments(ex)) == 0 ? decend(operation(ex)) : nothing + end, + function _chained_comparisons(ex, _...) + if head(ex) == :comparison && Symbol.(arguments(ex)[1:2:end]) ⊆ keys(comparison_operators) + str = join([isodd(i) ? "$var" : comparison_operators[Symbol(var)] for (i, var) in enumerate(decend.(vcat(operation(ex), arguments(ex))))], " ") + str = "\\left( $str \\right)" + return str + end + end, + function _wedge(ex, prevop, config) + head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\wedge $(decend(arguments(ex)[1]))" : nothing + end, + function _vee(ex, prevop, config) + head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\vee $(decend(arguments(ex)[1]))" : nothing + end, + function _negation(ex, prevop, config) + operation(ex) == :(!) ? "\\neg $(arguments(ex)[1])" : nothing + end, + function _kw(x, args...) + head(x) == :kw ? "$(decend(operation(x))) = $(decend(arguments(x)[1]))" : nothing + end, + function _parameters(x, args...) + head(x) == :parameters ? join(decend.(vcat(operation(x), arguments(x))), ", ") : nothing + end, + function _indexing(x, prevop, config) + if head(x) == :ref + if getconfig(:index) == :subscript + return "$(operation(x))_{$(join(arguments(x), ","))}" + elseif getconfig(:index) == :bracket + argstring = join(decend.(arguments(x)), ", ") + return "$(decend(operation(x)))\\left[$argstring\\right]" + else + throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket")) + end + end + end, + function _broadcast_macro(ex, prevop, config) + if head(ex) == :macrocall && operation(ex) == Symbol("@__dot__") + return decend(arguments(ex)[end]) + end + end, function _block(x, args...) - if head(x) == :block && length(filter(x->!(x isa LineNumberNode), arguments(x))) == 1 - return decend(arguments(x)[end]) + if head(x) == :block + return decend(vcat(operation(x), arguments(x))[end]) + end + end, + function number(x, prevop, config) + if x isa Number + try isinf(x) && return "\\infty" catch; end + fmt = getconfig(:fmt) + fmt isa String && (fmt = PrintfNumberFormatter(fmt)) + str = string(fmt(x)) + sign(x) == -1 && prevop == :^ && (str = surround(str)) + return str + end + end, + function rational_expr(x, prevop, config) + if operation(x) == :// + if arguments(x)[2] == 1 + return decend(arguments[1], prevop) + else + decend(:($(arguments(x)[1])/$(arguments(x)[2])), prevop) + end + end + end, + function rational(x, prevop, config) + if x isa Rational + str = x.den == 1 ? decend(x.num, prevop) : decend(:($(x.num)/$(x.den)), prevop) + prevop ∈ [:*, :^] && (str = surround(str)) + return str + end + end, + function complex(z, prevop, config) + if z isa Complex + str = "$(decend(z.re))$(z.im < 0 ? "-" : "+" )$(decend(abs(z.im)))\\textit{i}" + prevop ∈ [:*, :^] && (str = surround(str)) + return str end end, - function number(x, args...) - x isa Number ? string(x) : nothing + function _missing(x, prevop, config) + if ismissing(x) + "\\textrm{NA}" + end end, - function symbol(x, args...) - x isa Symbol ? string(x) : nothing + function symbol(sym, _, config) + if sym isa Symbol + str = string(sym == :Inf ? :∞ : sym) + str = convertSubscript(str) + getconfig(:convert_unicode) && (str = unicode2latex(str)) + return str + end end, function array(x, args...) x isa AbstractArray ? _latexarray(x) : nothing @@ -98,10 +252,11 @@ const MATCHING_FUNCTIONS = [ x isa Tuple ? _latexarray(x) : nothing end, function vect_exp(x, args...) - head(x)==:vect ? _latexarray(vcat(operation(x), arguments(x))) : nothing + head(x) ∈ [:vect, :vcat] ? _latexarray(expr_to_array(x)) : nothing end, function hcat_exp(x, args...) - head(x)==:hcat ? _latexarray(permutedims(vcat(operation(x), arguments(x)))) : nothing + # head(x)==:hcat ? _latexarray(permutedims(vcat(operation(x), arguments(x)))) : nothing + head(x)==:hcat ? _latexarray(expr_to_array(x)) : nothing end, (expr, prevop, config) -> begin h, op, args = unpack(expr) @@ -110,18 +265,54 @@ const MATCHING_FUNCTIONS = [ join(decend.(args), "") end end, - function call(expr, prevop, config) - h, op, args = unpack(expr) - if h == :call - string(get(Latexify.function2latex, op, op)) * "\\left( " * join(decend.(args, op), ",") * " \\right)" + function parse_string(str, prevop, config) + if str isa AbstractString + try + ex = Meta.parse(str) + return decend(ex, prevop) + catch ParseError + error(""" + in Latexify.jl: + You are trying to create latex-maths from a `String` that cannot be parsed as + an expression. + + `latexify` will, by default, try to parse any string inputs into expressions + and this parsing has just failed. + + If you are passing strings that you want returned verbatim as part of your input, + try making them `LaTeXString`s first. + + If you are trying to make a table with plain text, try passing the keyword + argument `latex=false`. You should also ensure that you have chosen an output + environment that is capable of displaying not-maths objects. Try for example + `env=:table` for a latex table or `env=:mdtable` for a markdown table. + """) + end end end, + # function symbol(sym, _, config) + # if sym isa Symbol + # str = string(sym == :Inf ? :∞ : sym) + # str = convertSubscript(str) + # convert_unicode && (str = unicode2latex(str)) + # return str + # end + # end, + function _tuple_expr(expr, prevop, config) + head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing + end, function strip_broadcast_dot(expr, prevop, config) h, op, args = unpack(expr) if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') return string(decend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) end end, + function strip_broadcast_dot_call(expr, prevop, config) + h, op, args = unpack(expr) + if expr isa Expr && config[:strip_broadcast] && h == :. + return decend(Expr(:call, op, args[1].args...), prevop) + end + end, function plusminus(expr, prevop, config) h, op, args = unpack(expr) if h == :call && op == :± @@ -159,10 +350,10 @@ const MATCHING_FUNCTIONS = [ return decend(arguments(args[1])[1], prevop) elseif args[1] isa Number && sign(args[1]) == -1 # return _latexraw(-args[1]; config...) - return decend(-args[1]; config...) + return decend(-args[1], op) else _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] - return "$op$_arg" + return prevop == :^ ? surround("$op$_arg") : "$op$_arg" end elseif length(args) == 2 @@ -207,4 +398,25 @@ const MATCHING_FUNCTIONS = [ ] # end -# const MATCHING_FUNCTIONS = default_matcher() \ No newline at end of file +# const MATCHING_FUNCTIONS = default_matcher() + + +function build_if_else_body(args, ifstr, elseifstr, elsestr) + _args = filter(x -> !(x isa LineNumberNode), args) + dargs = decend.(_args) + str = if length(_args) == 2 + """ + $(dargs[2]) & $ifstr $(dargs[1])""" + elseif length(_args) == 3 && head(_args[end]) == :elseif + """ + $(dargs[2]) & $ifstr $(dargs[1])\\\\ + $(dargs[3])""" + elseif length(_args) == 3 + """ + $(dargs[2]) & $ifstr $(dargs[1])\\\\ + $(dargs[3]) & $elsestr""" + else + throw(ArgumentError("Unexpected if/elseif/else statement to latexify. This could well be a Latexify.jl bug.")) + end + return str +end \ No newline at end of file From d8a574257e390abe9ed3ef20da04cd7ebc476cf8 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 9 Apr 2021 15:11:45 +0200 Subject: [PATCH 16/40] Fix kwarg treatment when latexifying functions. --- src/refactor_prototype_2.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/refactor_prototype_2.jl b/src/refactor_prototype_2.jl index 3ef32b94..013b2637 100644 --- a/src/refactor_prototype_2.jl +++ b/src/refactor_prototype_2.jl @@ -103,11 +103,18 @@ const MATCHING_FUNCTIONS = [ function call(expr, prevop, config) if expr isa Expr && head(expr) == :call h, op, args = unpack(expr) + if op isa Symbol - return string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) * "\\left( " * join(decend.(args, op), ", ") * " \\right)" + funcname = string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) + else + funcname = decend(op) + end + if head(args[1]) == :parameter + _arg = "\\left( " * join(decend.(args), ",") * " \\right)" else - return decend(op) * "\\left( " * join(decend.(args), ",") * " \\right)" + _arg = "\\left( $(join(decend.(args[2:end]), ",")); $(decend(args[1])) \\right)" end + return funcname * _arg end end, function _sqrt(ex, prevop, config) From 3ad951d05d0e1860ea31155dd6450c3cd9f8fa90 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 9 Apr 2021 16:22:07 +0200 Subject: [PATCH 17/40] Integrate refactor prototype into src properly. --- src/Latexify.jl | 48 ++-- src/config.jl | 36 +++ src/latexalign.jl | 2 +- src/latexarray.jl | 16 +- src/latexify_function.jl | 2 +- src/latexoperation.jl | 229 ----------------- src/latexraw.jl | 142 +++-------- ...tor_prototype_2.jl => latexraw_recipes.jl} | 115 +-------- src/utils.jl | 24 ++ test/latexraw_test.jl | 240 +++++++++++++----- 10 files changed, 316 insertions(+), 538 deletions(-) create mode 100644 src/config.jl delete mode 100644 src/latexoperation.jl rename src/{refactor_prototype_2.jl => latexraw_recipes.jl} (78%) diff --git a/src/Latexify.jl b/src/Latexify.jl index 8b6a1c5e..88c2054b 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -45,28 +45,15 @@ include("mdtext.jl") include("md.jl") include("utils.jl") +include("config.jl") +include("latexraw_recipes.jl") include("numberformatters.jl") include("latexify_function.jl") -include("refactor_prototype_2.jl") -export decend, unpack, head, operation, arguments, nested, default_matcher, LatexifyOperation +# include("refactor_prototype_2.jl") +export decend, unpack, head, operation, arguments, nested -### Add support for additional packages without adding them as dependencies. -function __init__() - @require DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" begin - include("plugins/ParameterizedFunctions.jl") - end - @require DiffEqBiological = "eb300fae-53e8-50a0-950c-e21f52c2b7e0" begin - include("plugins/DiffEqBiological.jl") - end - @require SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" begin - include("plugins/SymEngine.jl") - end - @require DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" begin - include("plugins/DataFrames.jl") - end -end macro generate_test(expr) return :(clipboard("@test $($(string(expr))) == replace(\nraw\"$($(esc(expr)))\", \"\\r\\n\"=>\"\\n\")\n")) @@ -127,4 +114,31 @@ macro append_test!(fname, str) ) end +# const _ex = :(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y)) +latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) +# Latexify._latextree(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) +# latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) +# latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) +# latexify(_ex) +# latexify(_ex) +# @assert precompile(latexify, (Expr,)) + +### Add support for additional packages without adding them as dependencies. +function __init__() + @require DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" begin + include("plugins/ParameterizedFunctions.jl") + end + @require DiffEqBiological = "eb300fae-53e8-50a0-950c-e21f52c2b7e0" begin + include("plugins/DiffEqBiological.jl") + end + @require SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" begin + include("plugins/SymEngine.jl") + end + @require DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" begin + include("plugins/DataFrames.jl") + end + # Latexify._latextree(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) + # Latexify.latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) end +end + diff --git a/src/config.jl b/src/config.jl new file mode 100644 index 00000000..37febf06 --- /dev/null +++ b/src/config.jl @@ -0,0 +1,36 @@ +const DEFAULT_CONFIG = Dict{Symbol, Any}( + :mulsym => " \\cdot ", + :convert_unicode => true, + :strip_broadcast => true, + :fmt => identity, + :index => :bracket, + :ifstr => "\\text{if }", + :elseifstr => "\\text{elseif }", + :elsestr => "\\text{otherwise}", +) + +## MODULE_CONFIG can store defaults specified in other modules. E.g. from recipes. +const MODULE_CONFIG = Dict{Symbol, Any}() + +## USE_CONFIG can store user-specified defaults +const USER_CONFIG = Dict{Symbol, Any}() + +## CONFIG is reset every latexify call. +const CONFIG = Dict{Symbol, Any}() +getconfig(key::Symbol) = CONFIG[key] + + +const comparison_operators = Dict( + :< => "<", + :.< => "<", + :> => ">", + :.> => ">", + Symbol("==") => "=", + Symbol(".==") => "=", + :<= => "\\leq", + :.<= => "\\leq", + :>= => "\\geq", + :.>= => "\\geq", + :!= => "\\neq", + :.!= => "\\neq", + ) \ No newline at end of file diff --git a/src/latexalign.jl b/src/latexalign.jl index d98125f0..ec5c813d 100644 --- a/src/latexalign.jl +++ b/src/latexalign.jl @@ -100,7 +100,7 @@ end Go through the elements, split at any = sign, pass on as a matrix. """ function _latexalign(vec::AbstractVector; kwargs...) - lvec = _latexraw.(vec; kwargs...) + lvec = latexraw.(vec; kwargs...) ## turn the array into a matrix lmat = reduce(hcat, split.(lvec, " = ")) ## turn the matrix ito arrays of left-hand-side, right-hand-side. diff --git a/src/latexarray.jl b/src/latexarray.jl index 7bc3e63c..77c48cc6 100644 --- a/src/latexarray.jl +++ b/src/latexarray.jl @@ -1,17 +1,3 @@ - -""" - latexarray{T}(arr::AbstractArray{T, 2}) -Create a LaTeX array environment using [`latexraw`](@ref). - -# Examples -```julia -arr = [1 2; 3 4] -latexarray(arr) -``` -```math -"\\begin{equation}\n\\left[\n\\begin{array}{cc}\n1 & 2\\\\ \n3 & 4\\\\ \n\\end{array}\n\\right]\n\\end{equation}\n" -``` -""" latexarray(args...; kwargs...) = latexify(args...;kwargs...,env=:array) function _latexarray(arr::AbstractArray; adjustment::Symbol=:c, transpose=false, double_linebreak=false, @@ -25,7 +11,7 @@ function _latexarray(arr::AbstractArray; adjustment::Symbol=:c, transpose=false, str = "\\left[\n" str *= "\\begin{array}{" * "$(adjustment)"^columns * "}\n" - arr = latexraw.(arr; kwargs...) + arr = decend.(arr) for i=1:rows, j=1:columns str *= arr[i,j] j==columns ? (str *= eol) : (str *= " & ") diff --git a/src/latexify_function.jl b/src/latexify_function.jl index 59d821e6..73957c20 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -24,7 +24,7 @@ const OUTPUTFUNCTIONS = Dict( :inline => _latexinline, :tabular => _latextabular, :table => _latextabular, - :raw => _latexraw, + :raw => latexraw, :array => _latexarray, :align => _latexalign, :aligned => (args...; kwargs...) -> _latexbracket(_latexalign(args...; kwargs..., aligned=true, starred=false); kwargs...), diff --git a/src/latexoperation.jl b/src/latexoperation.jl deleted file mode 100644 index cf4735f9..00000000 --- a/src/latexoperation.jl +++ /dev/null @@ -1,229 +0,0 @@ -""" - latexoperation(ex::Expr, prevOp::AbstractArray) - -Translate a simple operation given by `ex` to LaTeX maths syntax. -This uses the information about the previous operations to decide if -a parenthesis is needed. - -""" -function latexoperation(ex::Expr, prevOp::AbstractArray; cdot=true, index=:bracket, kwargs...)::String - op = ex.args[1] - args = map(i -> typeof(i) ∉ (String, LineNumberNode) ? latexraw(i; kwargs...) : i, ex.args) - - # Remove math italics for variables (i.e. words) longer than 2 characters. - # args = map(i -> (i isa String && all(map(isletter, collect(i))) && length(i) > 2) ? "{\\rm $i}" : i, args) - - if ex.head == :latexifymerge - if all(prevOp .== :none) - return join(args) - else - return "$(args[1])\\left( $(join(args[2:end])) \\right)" - end - end - - if op in [:/, :./] - return "\\frac{$(args[2])}{$(args[3])}" - - elseif op in [:*, :.*] - str="" - for i in 2:length(args) - arg = args[i] - prevOp[i] in [:+, :-, :±] && (arg = "\\left( $arg \\right)") - str = string(str, arg) - i != length(args) && (str *= cdot ? " \\cdot " : " ") - end - return str - - elseif op in [:+, :.+] - str = join(args[2:end], " + ") - str = replace(str, "+ -"=>"-") - str = replace(str, "+ -"=>"-") - return str - - elseif op in [:±, :.±] - return "$(args[2]) \\pm $(args[3])" - - elseif op in [:-, :.-] - if length(args) == 2 - if prevOp[2] == :none && string(args[2])[1] == '-' - return " + " * string(args[2])[2:end] - elseif prevOp[2] == :none && string(args[2])[1] == '+' - return " - " * string(args[2])[2:end] - elseif prevOp[2] in [:+, :-, :±] - return " - \\left( $(args[2]) \\right)" - end - return " - $(args[2])" - end - prevOp[3] in [:+, :-, :±] && (args[3] = "\\left( $(args[3]) \\right)") - - if prevOp[3] == :none && string(args[3])[1] == '-' - return "$(args[2]) + " * string(args[3])[2:end] - end - return "$(args[2]) - $(args[3])" - - elseif op in [:^, :.^] - #isa(args[2], String) && (args[2]="($(args[2]))") - if prevOp[2] in trigonometric_functions - str = get(function2latex, prevOp[2], "\\$(prevOp[2])") - return replace(args[2], str => "$(str)^{$(args[3])}") - end - if (prevOp[2] != :none) || (ex.args[2] isa Real && sign(ex.args[2]) == -1) || (ex.args[2] isa Complex) || (ex.args[2] isa Rational) - args[2]="\\left( $(args[2]) \\right)" - end - return "$(args[2])^{$(args[3])}" - elseif (ex.head in (:(=), :function)) && length(args) == 2 - return "$(args[1]) = $(args[2])" - elseif op == :(!) - return "\\neg $(args[2])" - end - - if ex.head == :. - ex.head = :call - # op = string(op, ".") ## Signifies broadcasting. - end - - string(op)[1] == '.' && (op = Symbol(string(op)[2:end])) - - # infix_operators = [:<, :>, Symbol("=="), :<=, :>=, :!=] - comparison_operators = Dict( - :< => "<", - :.< => "<", - :> => ">", - :.> => ">", - Symbol("==") => "=", - Symbol(".==") => "=", - :<= => "\\leq", - :.<= => "\\leq", - :>= => "\\geq", - :.>= => "\\geq", - :!= => "\\neq", - :.!= => "\\neq", - ) - - if op in keys(comparison_operators) && length(args) == 3 - str = "$(args[2]) $(comparison_operators[op]) $(args[3])" - str = "\\left( $str \\right)" - return str - end - - ### Check for chained comparison operators - if ex.head == :comparison && Symbol.(args[2:2:end]) ⊆ keys(comparison_operators) - str = join([isodd(i) ? "$var" : comparison_operators[var] for (i, var) in enumerate(Symbol.(args))], " ") - str = "\\left( $str \\right)" - return str - end - - if op in keys(function2latex) - return "$(function2latex[op])\\left( $(join(args[2:end], ", ")) \\right)" - end - - op == :sqrt && return "\\$op{$(args[2])}" - op == :abs && return "\\left\\|$(args[2])\\right\\|" - op == :exp && return "e^{$(args[2])}" - - ## Leave math italics for single-character operator names (e.g., f(x)). - opname = replace(string(op), '_'=>raw"\_") - if length(opname) > 1 - opname = "\\mathrm{$opname}" - end - - if ex.head == :ref - if index == :subscript - return "$(args[1])_{$(join(args[2:end], ","))}" - elseif index == :bracket - argstring = join(args[2:end], ", ") - return "$opname\\left[$argstring\\right]" - else - throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket")) - end - end - - if ex.head == :macrocall && ex.args[1] == Symbol("@__dot__") - return string(ex.args[end]) - end - - if ex.head == :call - if length(args) == 1 - return "$opname()" - elseif args[2] isa String && occursin("=", args[2]) - return "$opname\\left( $(join(args[3:end], ", ")); $(args[2]) \\right)" - else - return "$opname\\left( $(join(args[2:end], ", ")) \\right)" - end - end - - if ex.head == :tuple - # return "\\left(" * join(ex.args, ", ") * "\\right)" - return join(ex.args, ", ") - end - - ex.head == Symbol("'") && return "$(args[1])'" - - ## Enable the parsing of kwargs in a function definition - ex.head == :kw && return "$(args[1]) = $(args[2])" - ex.head == :parameters && return join(args, ", ") - - ## Use the last expression in a block. - ## This is somewhat shady but it helps with latexifying functions. - ex.head == :block && return args[end] - - ## Sort out type annotations. Mainly for function arguments. - ex.head == :(::) && length(args) == 1 && return "::$(args[1])" - ex.head == :(::) && length(args) == 2 && return "$(args[1])::$(args[2])" - - ## Pass back values that were explicitly returned. - ex.head == :return && length(args) == 1 && return args[1] - - ## Case enviroment for if statements and ternary ifs. - if ex.head in (:if, :elseif) - textif::String = "\\text{if }" - begincases::String = ex.head == :if ? "\\begin{cases}\n" : "" - endcases::String = ex.head == :if ? "\n\\end{cases}" : "" - if length(args) == 3 - # Check if already parsed elseif as args[3] - haselseif::Bool = occursin(Regex("\\$textif"), args[3]) - otherwise::String = haselseif ? "" : " & \\text{otherwise}" - return """$begincases$(args[2]) & $textif $(args[1])\\\\ - $(args[3])$otherwise$endcases""" - elseif length(args) == 2 - return "$begincases$(args[2]) & $textif $(args[1])$endcases" - end - end - - ## Conditional operators converted to logical operators. - ex.head == :(&&) && length(args) == 2 && return "$(args[1]) \\wedge $(args[2])" - ex.head == :(||) && length(args) == 2 && return "$(args[1]) \\vee $(args[2])" - - - - ## if we have reached this far without a return, then error. - error("Latexify.jl's latexoperation does not know what to do with one of the - expressions provided ($ex).") - return "" -end - -latexoperation(sym::Symbol, prevOp::AbstractArray; kwargs...) = "$sym" - - -function convertSubscript!(ex::Expr) - for i in 1:length(ex.args) - arg = ex.args[i] - if arg isa Symbol - ex.args[i] = convertSubscript(arg) - end - end - return nothing -end - -function convertSubscript(str::String) - if occursin("_", str) - subscriptList = split(str, "_") - subscript = join(subscriptList[2:end], "\\_") - result = "$(subscriptList[1])_{$subscript}" - else - result = str - end - return result -end - -convertSubscript(sym::Symbol) = convertSubscript(string(sym)) diff --git a/src/latexraw.jl b/src/latexraw.jl index 51fbe52f..ce4f8bcb 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -1,119 +1,45 @@ -@doc doc""" - latexraw(arg) - -Generate LaTeX equations from `arg`. - -Parses expressions, ParameterizedFunctions, SymEngine.Base and arrays thereof. -Returns a string formatted for LaTeX. - -# Examples - -## using expressions -```jldoctest -expr = :(x/(y+x)) -latexraw(expr) - -# output - -"\\frac{x}{y + x}" -``` - -```jldoctest -expr = Meta.parse("x/(y+x)") -latexraw(expr) - -# output - -"\\frac{x}{y + x}" -``` - -## using ParameterizedFunctions -```julia -using DifferentialEquations; -f = @ode_def feedback begin - dx = y/c_1 - x - dy = x^c_2 - y - end c_1=>1.0 c_2=>1.0 -latexraw(f) - -# output - -2-element Array{String,1}: - "dx/dt = \\frac{y}{c_{1}} - x" - "dy/dt = x^{c_{2}} - y" -``` - -## using SymEngine -```jldoctest -using SymEngine -@vars x y -symExpr = x + x + x*y*y -latexraw(symExpr) - -# output - -"2 \\cdot x + x \\cdot y^{2}" -``` -""" -# latexraw(args...; kwargs...) = latexify(args...; kwargs..., env=:raw) -latexraw(args...; kwargs...) = _latexraw(args...; kwargs...) - -_latexraw(ex::Expr; kwargs...) = _latextree(ex; kwargs...) -_latexraw(ex; kwargs...) = nested(ex) ? _latextree(ex; kwargs...) : throw(ArgumentError("Unsupported type $(typeof(ex)) to Latexify._latexraw")) - - -function _latexraw(args...; kwargs...) - @assert length(args) > 1 "latexify does not support objects of type $(typeof(args[1]))." - _latexraw(args; kwargs...) +function latexraw(expr; kwargs...) + empty!(CONFIG) + merge!(CONFIG, DEFAULT_CONFIG, kwargs) + str = decend(expr) + CONFIG[:convert_unicode] && (str = unicode2latex(str)) + return LaTeXString(str) end -_latexraw(arr::Union{AbstractArray, Tuple}; kwargs...) = _latexarray(arr; kwargs...) -_latexraw(i::Nothing; kwargs...) = "" -_latexraw(i::SubString; kwargs...) = latexraw(Meta.parse(i); kwargs...) -_latexraw(i::SubString{LaTeXStrings.LaTeXString}; kwargs...) = i -_latexraw(i::Rational; kwargs...) = i.den == 1 ? latexraw(i.num; kwargs...) : latexraw(:($(i.num)/$(i.den)); kwargs...) -_latexraw(z::Complex; kwargs...) = LaTeXString("$(latexraw(z.re;kwargs...))$(z.im < 0 ? "-" : "+" )$(latexraw(abs(z.im);kwargs...))\\textit{i}") -#latexraw(i::DataFrames.DataArrays.NAtype) = "\\textrm{NA}" -_latexraw(str::LaTeXStrings.LaTeXString; kwargs...) = str -function _latexraw(i::Number; fmt=PlainNumberFormatter(), kwargs...) - try isinf(i) && return LaTeXString("\\infty") catch; end - fmt isa String && (fmt = PrintfNumberFormatter(fmt)) - return LaTeXString(fmt(i)) -end +add_matcher(f) = push!(MATCHING_FUNCTIONS, f) -function _latexraw(i::Char; convert_unicode=true, kwargs...) - LaTeXString(convert_unicode ? unicode2latex(string(i)) : string(i)) -end +# _check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op +# _check_call_match(e, op::AbstractArray) = e isa Expr && e.head === :call && e.args[1] ∈ op + +# # User would write: +# # text_color_latexify(e) = "\\textcolor{$(args[1])}{$(latexify(args[2]))}" +# # add_call_matcher(text_color_latexify, :textcolor) +# function add_call_matcher(op, f::Function) +# push!(MATCHING_FUNCTIONS, (expr, prevop, config) -> _check_call_match(expr, op) ? f(expr, prevop, config) : nothing) +# end -function _latexraw(i::Symbol; convert_unicode=true, kwargs...) - str = string(i == :Inf ? :∞ : i) - str = convertSubscript(str) - convert_unicode && (str = unicode2latex(str)) - return LaTeXString(str) -end -function _latexraw(i::String; kwargs...) - try - ex = Meta.parse(i) - return latexraw(ex; kwargs...) - catch ParseError - error(""" -in Latexify.jl: -You are trying to create latex-maths from a `String` that cannot be parsed as -an expression. +nested(::Any) = false +arguments(::Any) = nothing +operation(::Any) = nothing +head(::Any) = nothing -`latexify` will, by default, try to parse any string inputs into expressions -and this parsing has just failed. +nested(::Expr) = true +arguments(ex::Expr) = ex.args[2:end] +operation(ex::Expr) = ex.args[1] +head(ex::Expr) = ex.head -If you are passing strings that you want returned verbatim as part of your input, -try making them `LaTeXString`s first. +unpack(x) = (head(x), operation(x), arguments(x)) -If you are trying to make a table with plain text, try passing the keyword -argument `latex=false`. You should also ensure that you have chosen an output -environment that is capable of displaying not-maths objects. Try for example -`env=:table` for a latex table or `env=:mdtable` for a markdown table. -""") +function decend(e, prevop=Val(:_nothing))::String + for f in MATCHING_FUNCTIONS[end:-1:1] + call_result = f(e, prevop, CONFIG) + if !isnothing(call_result) + return call_result + break + end end + throw(ArgumentError("No matching expression conversion function for $e")) end -_latexraw(i::Missing; kwargs...) = "\\textrm{NA}" +surround(x) = "\\left( $x \\right)" \ No newline at end of file diff --git a/src/refactor_prototype_2.jl b/src/latexraw_recipes.jl similarity index 78% rename from src/refactor_prototype_2.jl rename to src/latexraw_recipes.jl index 013b2637..b48e610c 100644 --- a/src/refactor_prototype_2.jl +++ b/src/latexraw_recipes.jl @@ -1,95 +1,4 @@ - -add_matcher(f) = push!(MATCHING_FUNCTIONS, f) - -_check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op -_check_call_match(e, op::AbstractArray) = e isa Expr && e.head === :call && e.args[1] ∈ op - -# User would write: -# text_color_latexify(e) = "\\textcolor{$(args[1])}{$(latexify(args[2]))}" -# add_call_matcher(text_color_latexify, :textcolor) -function add_call_matcher(op, f::Function) - push!(MATCHING_FUNCTIONS, (expr, prevop, config) -> _check_call_match(expr, op) ? f(expr, prevop, config) : nothing) -end - - -nested(::Any) = false -arguments(::Any) = nothing -operation(::Any) = nothing -head(::Any) = nothing - -unpack(x) = (head(x), operation(x), arguments(x)) - -nested(::Expr) = true -arguments(ex::Expr) = ex.args[2:end] -operation(ex::Expr) = ex.args[1] -head(ex::Expr) = ex.head - - -const DEFAULT_CONFIG = Dict{Symbol, Any}( - :mulsym => " \\cdot ", - :convert_unicode => true, - :strip_broadcast => true, - :fmt => identity, - :index => :bracket, - :ifstr => "\\text{if }", - :elseifstr => "\\text{elseif }", - :elsestr => "\\text{otherwise}", -) -const CONFIG = Dict{Symbol, Any}() - -getconfig(key::Symbol) = CONFIG[key] -function _latextree(expr; kwargs...) - empty!(CONFIG) - merge!(CONFIG, DEFAULT_CONFIG, kwargs) - str = decend(expr) - CONFIG[:convert_unicode] && (str = unicode2latex(str)) - return LaTeXString(str) -end -# decend(x, prevop) = string(x) -# decend(x::Expr, prevop::Symbol) = decend(x, Val{prevop}()) -function decend(e, prevop=Val(:_nothing))::String - # if nested(e) - # nested(e) || return Latexify.latexraw(e; kwargs...) - for f in MATCHING_FUNCTIONS[end:-1:1] - call_result = f(e, prevop, CONFIG) - if !isnothing(call_result) - return call_result - break - end - end - throw(ArgumentError("No matching expression conversion function for $e")) - # else - # return _latexraw(e; CONFIG...) - # end -end - - -### Overloadable formatting functions -surround(x) = "\\left( $x \\right)" - -# match_equals(e) = (e isa Expr && e.head === :=) ? - # "$(latexify(value(lhs_val))) = $(latexify(first(rhs_array)))" : - # nothing - -# function default_matcher() -# return - -const comparison_operators = Dict( - :< => "<", - :.< => "<", - :> => ">", - :.> => ">", - Symbol("==") => "=", - Symbol(".==") => "=", - :<= => "\\leq", - :.<= => "\\leq", - :>= => "\\geq", - :.>= => "\\geq", - :!= => "\\neq", - :.!= => "\\neq", - ) - const MATCHING_FUNCTIONS = [ function report_bad_call(expr, prevop, config) println(""" @@ -109,10 +18,10 @@ const MATCHING_FUNCTIONS = [ else funcname = decend(op) end - if head(args[1]) == :parameter - _arg = "\\left( " * join(decend.(args), ",") * " \\right)" + if head(args[1]) == :parameters + _arg = "\\left( $(join(decend.(args[2:end]), ", ")); $(decend(args[1])) \\right)" else - _arg = "\\left( $(join(decend.(args[2:end]), ",")); $(decend(args[1])) \\right)" + _arg = "\\left( " * join(decend.(args), ", ") * " \\right)" end return funcname * _arg end @@ -187,7 +96,7 @@ const MATCHING_FUNCTIONS = [ function _indexing(x, prevop, config) if head(x) == :ref if getconfig(:index) == :subscript - return "$(operation(x))_{$(join(arguments(x), ","))}" + return "$(operation(x))_{$(join(arguments(x), ", "))}" elseif getconfig(:index) == :bracket argstring = join(decend.(arguments(x)), ", ") return "$(decend(operation(x)))\\left[$argstring\\right]" @@ -244,10 +153,13 @@ const MATCHING_FUNCTIONS = [ "\\textrm{NA}" end end, + function _nothing(x, prevop, config) + isnothing(x) ? "" : nothing + end, function symbol(sym, _, config) if sym isa Symbol str = string(sym == :Inf ? :∞ : sym) - str = convertSubscript(str) + str = convert_subscript(str) getconfig(:convert_unicode) && (str = unicode2latex(str)) return str end @@ -297,14 +209,6 @@ const MATCHING_FUNCTIONS = [ end end end, - # function symbol(sym, _, config) - # if sym isa Symbol - # str = string(sym == :Inf ? :∞ : sym) - # str = convertSubscript(str) - # convert_unicode && (str = unicode2latex(str)) - # return str - # end - # end, function _tuple_expr(expr, prevop, config) head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing end, @@ -403,9 +307,6 @@ const MATCHING_FUNCTIONS = [ end end, ] -# end - -# const MATCHING_FUNCTIONS = default_matcher() function build_if_else_body(args, ifstr, elseifstr, elsestr) diff --git a/src/utils.jl b/src/utils.jl index 30601dfb..e5473298 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -193,6 +193,30 @@ function render(s::LaTeXString, ::MIME"image/svg"; debug=false, name=tempname(), end +function convert_subscript!(ex::Expr) + for i in 1:length(ex.args) + arg = ex.args[i] + if arg isa Symbol + ex.args[i] = convert_subscript(arg) + end + end + return nothing +end + +function convert_subscript(str::String) + if occursin("_", str) + subscript_list = split(str, "_") + subscript = join(subscript_list[2:end], "\\_") + result = "$(subscript_list[1])_{$subscript}" + else + result = str + end + return result +end + +convert_subscript(sym::Symbol) = convert_subscript(string(sym)) + + function expr_to_array(ex) ex.head == :typed_vcat && (ex = Expr(:vcat, ex.args[2:end]...)) ex.head == :typed_hcat && (ex = Expr(:hcat, ex.args[2:end]...)) diff --git a/test/latexraw_test.jl b/test/latexraw_test.jl index 2f737ab0..e84b81c9 100644 --- a/test/latexraw_test.jl +++ b/test/latexraw_test.jl @@ -1,4 +1,3 @@ - using Latexify using Test using Markdown @@ -8,6 +7,13 @@ ex = :(2*x^2 - y/c_2) desired_output = "2 \\cdot x^{2} - \\frac{y}{c_{2}}" + +latexify(:([1 2; 2 3])) + + +latexify(:(a = [1,2])) +latexify(:(a = [1 2;2 3])) +:([1;2]).head @test latexify(:(a = [x / y; 3; 4])) == replace( raw"$a = \left[ \begin{array}{c} @@ -32,6 +38,17 @@ raw"$a = \left[ \end{array} \right]$", "\r\n"=>"\n") +@test latexify(:(x < y < x)) == replace( +raw"$\left( x < y < x \right)$", "\r\n"=>"\n") + +:(f(x; k=3)) +latexify( +:(f(x; k=3.283926498236)), fmt = FancyNumberFormatter(2) +) + + +@test latexify(:(x <= y)) == replace( +raw"$\left( x \leq y \right)$", "\r\n"=>"\n") @test latexraw(str) == latexraw(ex) @test latexraw(ex) == desired_output @@ -44,7 +61,7 @@ array_test = [ex, str] @test latexraw(:(@__dot__(x / y))) == raw"\frac{x}{y}" @test latexraw(:(@. x / y)) == raw"\frac{x}{y}" -@test latexraw(:(eps())) == raw"\mathrm{eps}()" +@test latexraw(:(eps())) == raw"eps\left( \right)" @test latexraw(:y_c_a) == "y_{c\\_a}" @test latexraw(1.0) == "1.0" @@ -72,13 +89,22 @@ array_test = [ex, str] @test latexraw(:(acsch(x))) == "\\mathrm{arccsch}\\left( x \\right)" @test latexraw(:(x ± y)) == "x \\pm y" @test latexraw(:(f(x))) == "f\\left( x \\right)" +@test latexify(:(hello_there_hi(x))) == replace( +raw"$hello\_there\_hi\left( x \right)$", "\r\n"=>"\n") +@test latexify(:((hi(hello_there_hi))(x))) == replace( +raw"$hi\left( hello_{there\_hi} \right)\left( x \right)$", "\r\n"=>"\n") @test latexraw("x = 4*y") == "x = 4 \\cdot y" @test latexraw(:(sqrt(x))) == "\\sqrt{x}" +@test latexraw(:(abs(x))) == raw"\left\|x\right\|" @test latexraw(complex(1,-1)) == "1-1\\textit{i}" @test latexraw(1//2) == "\\frac{1}{2}" +@test latexraw(:(1//2)) == "\\frac{1}{2}" @test latexraw(missing) == "\\textrm{NA}" @test latexraw("x[2]") == raw"x\left[2\right]" @test latexraw("x[2, 3]") == raw"x\left[2, 3\right]" +# Latexify.@generate_test latexify(:(x[2//3, x/y])) +@test latexify(:(x[2 // 3, x / y])) == replace( +raw"$x\left[\frac{2}{3}, \frac{x}{y}\right]$", "\r\n"=>"\n") @test latexraw("α") == raw"\alpha" @test latexraw("α + 1") == raw"\alpha + 1" @test latexraw("α₁") == raw"\alpha{_1}" @@ -93,8 +119,9 @@ array_test = [ex, str] @test latexraw(Inf) == raw"\infty" @test latexraw(:Inf) == raw"\infty" @test latexraw("Inf") == raw"\infty" - - +:((-1) ^ 2).args +latexify(:((-1) ^ 2)) +latexify(:((-x) ^ 2)) @test latexify(:((-1) ^ 2)) == replace( raw"$\left( -1 \right)^{2}$", "\r\n"=>"\n") @test latexify(:($(1 + 2im) ^ 2)) == replace( @@ -102,24 +129,103 @@ raw"$\left( 1+2\textit{i} \right)^{2}$", "\r\n"=>"\n") @test latexify(:($(3 // 2) ^ 2)) == replace( raw"$\left( \frac{3}{2} \right)^{2}$", "\r\n"=>"\n") - ### Test broadcasting -@test latexraw(:(sum.((a, b)))) == raw"\mathrm{sum}\left( a, b \right)" - - - - +@test latexraw(:(sum.((a, b)))) == raw"sum\left( a, b \right)" +@test latexraw(:(sum.(a, b))) == raw"sum\left( a, b \right)" + +# Latexify.@generate_test latexify(:(-(-(-(-x))))) +@test latexify(:(-(-(-(-x))))) == replace( +raw"$x$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(-(-(-(-1)))))) +@test latexify(:(-(-(-(-(-1)))))) == replace( +raw"$-1$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(-(-(-1))))) +@test latexify(:(-(-(-(-1))))) == replace( +raw"$1$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(-(-(-(-x)))))) +@test latexify(:(-(-(-(-(-x)))))) == replace( +raw"$-x$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(-(-(-x))))) +@test latexify(:(-(-(-(-x))))) == replace( +raw"$x$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(-(-(-(x + y)))))) +@test latexify(:(-(-(-(-((x + y))))))) == replace( +raw"$x + y$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(-(-(x + y))))) +@test latexify(:(-(-(-((x + y)))))) == replace( +raw"$-\left( x + y \right)$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(x + y))) +@test latexify(:(-((x + y)))) == replace( +raw"$-\left( x + y \right)$", "\r\n"=>"\n") + +# Latexify.@generate_test latexify(:(-(1))) +@test latexify(:(-1)) == replace( +raw"$-1$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(-1))) +@test latexify(:(- -1)) == replace( +raw"$1$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-1 -(-3))) +@test latexify(:(-1 - -3)) == replace( +raw"$-1 + 3$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-1 -(1-3))) +@test latexify(:(-1 - (1 - 3))) == replace( +raw"$-1 - \left( 1 - 3 \right)$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-1 -(-(1-3)))) +@test latexify(:(-1 - -((1 - 3)))) == replace( +raw"$-1 + 1 - 3$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-x -(-(y-x)))) +@test latexify(:(-x - -((y - x)))) == replace( +raw"$-x + y - x$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-x -(y-x))) +@test latexify(:(-x - (y - x))) == replace( +raw"$-x - \left( y - x \right)$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-(1+1))) +@test latexify(:(-((1 + 1)))) == replace( +raw"$-\left( 1 + 1 \right)$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(1 + (-(2)))) +@test latexify(:(1 + -2)) == replace( +raw"$1 -2$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(1 + (-2))) +@test latexify(:(1 + -2)) == replace( +raw"$1 -2$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(1 + -2)) +@test latexify(:(1 + -2)) == replace( +raw"$1 -2$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(1 + (-2 -3 -4))) +@test latexify(:(1 + ((-2 - 3) - 4))) == replace( +raw"$1 -2 - 3 - 4$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(1 + 1*(-2 -3 -4))) +@test latexify(:(1 + 1 * ((-2 - 3) - 4))) == replace( +raw"$1 + 1 \cdot \left( -2 - 3 - 4 \right)$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(1 -2 -3 -4)) +@test latexify(:(((1 - 2) - 3) - 4)) == replace( +raw"$1 - 2 - 3 - 4$", "\r\n"=>"\n") +# Latexify.@generate_test latexify("1 - 2 - (- 3 -(2 - 8) + 4)") +@test latexify("1 - 2 - (- 3 -(2 - 8) + 4)") == replace( +raw"$1 - 2 - \left( -3 - \left( 2 - 8 \right) + 4 \right)$", "\r\n"=>"\n") +# Latexify.@generate_test latexify("-1 -(-3)") +@test latexify("-1 -(-3)") == replace( +raw"$-1 + 3$", "\r\n"=>"\n") +# Latexify.@generate_test latexify("-1 -(-(-(-(3))))") +@test latexify("-1 -(-(-(-(3))))") == replace( +raw"$-1 + 3$", "\r\n"=>"\n") +# Latexify.@generate_test latexify("-y") +@test latexify("-y") == replace( +raw"$-y$", "\r\n"=>"\n") +# Latexify.@generate_test latexify(:(-1)) +@test latexify(:(-1)) == replace( +raw"$-1$", "\r\n"=>"\n") ### Test for correct signs in nested sums/differences. -@test latexraw("-(-1)") == raw" + 1" +@test latexraw("-(-1)") == raw"1" @test latexraw("+(-1)") == raw"-1" -@test latexraw("-(+1)") == raw" - 1" -@test latexraw("-(1+1)") == raw" - \left( 1 + 1 \right)" +@test latexraw("-(+1)") == raw"-1" +@test latexraw("-(1+1)") == raw"-\left( 1 + 1 \right)" @test latexraw("1-(-2)") == raw"1 + 2" -@test latexraw("1 + (-(2))") == raw"1 - 2" +@test latexraw("1 + (-(2))") == raw"1 -2" @test latexraw("1 + (-2 -3 -4)") == raw"1 -2 - 3 - 4" -@test latexraw("1 - 2 - (- 3 - 4)") == raw"1 - 2 - \left( - 3 - 4 \right)" -@test latexraw("1 - 2 - (- 3 -(2) + 4)") == raw"1 - 2 - \left( - 3 - 2 + 4 \right)" -@test latexraw("1 - 2 - (- 3 -(2 - 8) + 4)") == raw"1 - 2 - \left( - 3 - \left( 2 - 8 \right) + 4 \right)" +@test latexraw("1 - 2 - (- 3 - 4)") == raw"1 - 2 - \left( -3 - 4 \right)" +@test latexraw("1 - 2 - (- 3 -(2) + 4)") == raw"1 - 2 - \left( -3 - 2 + 4 \right)" +@test latexraw("1 - 2 - (- 3 -(2 - 8) + 4)") == raw"1 - 2 - \left( -3 - \left( 2 - 8 \right) + 4 \right)" # @test_throws ErrorException latexify("x/y"; env=:raw, bad_kwarg="should error") @@ -128,6 +234,9 @@ raw"$\left( \frac{3}{2} \right)^{2}$", "\r\n"=>"\n") raw"3 \cdot \left( a < b \leq c < d \leq e > f \leq g \leq h < i = j = k \neq l \neq m \right)" +latexify(:(α)) +latexify(:∞) +latexify(:Inf) #### Test the fmt keyword option @test latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:align, fmt="%.2e") == replace( @@ -180,73 +289,74 @@ test_functions = [:sinh, :alpha, :Theta, :cosc, :acoth, :acot, :asech, :lambda, :acsch, :theta, :asec, :Sigma, :sin] +# Latexify.@generate_test latexify(["3*$(func)(x)^2/4 -1" for func = test_functions]) @test latexify(["3*$(func)(x)^2/4 -1" for func = test_functions]) == replace( raw"\begin{equation} \left[ \begin{array}{c} \frac{3 \cdot \sinh^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \alpha\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \Theta\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \alpha\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Theta\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{cosc}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arccoth}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arccot}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arcsech}^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \lambda\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \lambda\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{arcsinh}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{sinc}^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \eta\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \kappa\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \nu\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \eta\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \kappa\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \nu\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \arcsin^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \epsilon\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \sigma\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \upsilon\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \phi\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \epsilon\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \sigma\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \upsilon\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \phi\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \tanh^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \iota\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \Psi\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \iota\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Psi\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{arccosh}^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \log\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \zeta\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \mu\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \log\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \zeta\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \mu\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \csc^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \xi\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \tau\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \beta\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \Lambda\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \Xi\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \Phi\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \xi\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \tau\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \beta\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Lambda\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Xi\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Phi\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{arccsc}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \arctan^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{sech}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arctanh}^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \Gamma\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \Delta\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \rho\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Gamma\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Delta\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \rho\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \sec^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \log_{10}\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \delta\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \pi\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \log_{10}\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \delta\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \pi\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \cot^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \log_{2}\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \log_{2}\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \cos^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \Omega\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \psi\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Omega\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \psi\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \arctan^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \Gamma\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Gamma\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \cosh^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \arccos^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \Pi\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \Upsilon\left( x \right) \right)^{2}}{4} - 1 \\ -\frac{3 \cdot \left( \omega\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Pi\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Upsilon\left( x \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \omega\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \coth^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \chi\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \chi\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \tan^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{csch}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arccsch}^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \theta\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \theta\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{arcsec}^{2}\left( x \right)}{4} - 1 \\ -\frac{3 \cdot \left( \Sigma\left( x \right) \right)^{2}}{4} - 1 \\ +\frac{3 \cdot \Sigma\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \sin^{2}\left( x \right)}{4} - 1 \\ \end{array} \right] @@ -259,7 +369,6 @@ raw"\begin{equation} @test latexraw(:(x || y)) == "x \\vee y" @test latexraw(:(x || !y)) == "x \\vee \\neg y" - ## Test {cases} enviroment @test latexraw(:(R(p,e,d) = e ? 0 : log(p) - d)) == replace( raw"R\left( p, e, d \right) = \begin{cases} @@ -270,7 +379,7 @@ raw"R\left( p, e, d \right) = \begin{cases} @test latexraw(:(R(p,e,d,t) = if (t && e); 0 elseif (t && !e); d else log(p) end)) == replace( raw"R\left( p, e, d, t \right) = \begin{cases} 0 & \text{if } t \wedge e\\ -d & \text{if } t \wedge \neg e\\ +d & \text{elseif } t \wedge \neg e\\ \log\left( p \right) & \text{otherwise} \end{cases}", "\r\n"=>"\n") @@ -287,10 +396,21 @@ d & \text{if } t \wedge \neg e\\ return log(p) end end)) == replace( -raw"\mathrm{reward}\left( p, e, d, t \right) = \begin{cases} +raw"reward\left( p, e, d, t \right) = \begin{cases} 0 & \text{if } t \wedge e\\ --1 \cdot d & \text{if } t \wedge \neg e\\ --2 \cdot d & \text{if } 2 \cdot t \wedge e\\ --3 \cdot d & \text{if } 3 \cdot t \wedge e\\ +-1 \cdot d & \text{elseif } t \wedge \neg e\\ +-2 \cdot d & \text{elseif } 2 \cdot t \wedge e\\ +-3 \cdot d & \text{elseif } 3 \cdot t \wedge e\\ \log\left( p \right) & \text{otherwise} \end{cases}", "\r\n"=>"\n") + +@test latexify(:(f(x; y = 2))) == replace( +raw"$f\left( x; y = 2 \right)$", "\r\n"=>"\n") + +@test latexraw(nothing) == raw"" + +str = "hi x = 3 bye" +@test latexraw(SubString(str, 4,8)) == raw"x = 3" + +lstr = Latexify.L"hi x = 3 bye" +@test latexraw(SubString(lstr, 5,9)) == raw"x = 3" \ No newline at end of file From 72553750ca9cf974a89f737a87b8931e41e9fd68 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 9 Apr 2021 16:26:50 +0200 Subject: [PATCH 18/40] Add type annotation support. --- src/latexraw_recipes.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/latexraw_recipes.jl b/src/latexraw_recipes.jl index b48e610c..30cc0410 100644 --- a/src/latexraw_recipes.jl +++ b/src/latexraw_recipes.jl @@ -78,6 +78,15 @@ const MATCHING_FUNCTIONS = [ return str end end, + function _type_annotation(ex, prevop, config) + if head(ex) == :(::) + if length(arguments(ex)) == 0 + return "::$(operation(ex))" + elseif length(arguments(ex)) == 1 + return "$(operation(ex))::$(arguments(ex)[1])" + end + end + end, function _wedge(ex, prevop, config) head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\wedge $(decend(arguments(ex)[1]))" : nothing end, From 9f1a3f91a575a665e5ae42568502717d449b5c40 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 9 Apr 2021 17:00:40 +0200 Subject: [PATCH 19/40] Fix and update tests. Recipes still broken. --- src/config.jl | 4 ++++ src/latexarray.jl | 20 ++++++++++++-------- src/latexify_function.jl | 2 ++ src/latexraw_recipes.jl | 10 ++++++++-- test/cdot_test.jl | 24 ++++++++++++------------ test/latexify_test.jl | 15 +++++++-------- test/macros.jl | 8 ++++---- test/runtests.jl | 4 ++-- test/utils_test.jl | 16 +++++++++------- 9 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/config.jl b/src/config.jl index 37febf06..faa149f8 100644 --- a/src/config.jl +++ b/src/config.jl @@ -7,6 +7,10 @@ const DEFAULT_CONFIG = Dict{Symbol, Any}( :ifstr => "\\text{if }", :elseifstr => "\\text{elseif }", :elsestr => "\\text{otherwise}", + :adjustment => "c", + :transpose => false, + :double_linebreak => false, + :starred => false, ) ## MODULE_CONFIG can store defaults specified in other modules. E.g. from recipes. diff --git a/src/latexarray.jl b/src/latexarray.jl index 77c48cc6..53d94e60 100644 --- a/src/latexarray.jl +++ b/src/latexarray.jl @@ -1,7 +1,11 @@ latexarray(args...; kwargs...) = latexify(args...;kwargs...,env=:array) -function _latexarray(arr::AbstractArray; adjustment::Symbol=:c, transpose=false, double_linebreak=false, - starred=false, kwargs...) +function _latexarray(arr::AbstractArray) + adjustment = getconfig(:adjustment) + transpose = getconfig(:transpose) + double_linebreak = getconfig(:double_linebreak) + starred = getconfig(:starred) + transpose && (arr = permutedims(arr)) rows = first(size(arr)) columns = length(size(arr)) > 1 ? size(arr)[2] : 1 @@ -25,13 +29,13 @@ function _latexarray(arr::AbstractArray; adjustment::Symbol=:c, transpose=false, end -_latexarray(args::AbstractArray...; kwargs...) = _latexarray(reduce(hcat, args); kwargs...) -_latexarray(arg::AbstractDict; kwargs...) = _latexarray(collect(keys(arg)), collect(values(arg)); kwargs...) -_latexarray(arg::Tuple...; kwargs...) = _latexarray([collect(i) for i in arg]...; kwargs...) +_latexarray(args::AbstractArray...) = _latexarray(reduce(hcat, args)) +_latexarray(arg::AbstractDict) = _latexarray(collect(keys(arg)), collect(values(arg))) +_latexarray(arg::Tuple...) = _latexarray([collect(i) for i in arg]...) -function _latexarray(arg::Tuple; kwargs...) +function _latexarray(arg::Tuple) if first(arg) isa Tuple || first(arg) isa AbstractArray - return _latexarray([collect(i) for i in arg]...; kwargs...) + return _latexarray([collect(i) for i in arg]...) end - return _latexarray(collect(arg); kwargs...) + return _latexarray(collect(arg)) end diff --git a/src/latexify_function.jl b/src/latexify_function.jl index 73957c20..44b966c7 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -3,6 +3,8 @@ function latexify(args...; kwargs...) ## Let potential recipes transform the arguments. args, kwargs = apply_recipe(args...; default_kwargs..., kwargs...) + empty!(CONFIG) + merge!(CONFIG, DEFAULT_CONFIG, kwargs) ## If the environment is unspecified, use auto inference. env = get(kwargs, :env, :auto) diff --git a/src/latexraw_recipes.jl b/src/latexraw_recipes.jl index 30cc0410..572b6df2 100644 --- a/src/latexraw_recipes.jl +++ b/src/latexraw_recipes.jl @@ -18,7 +18,7 @@ const MATCHING_FUNCTIONS = [ else funcname = decend(op) end - if head(args[1]) == :parameters + if length(args) >= 1 && head(args[1]) == :parameters _arg = "\\left( $(join(decend.(args[2:end]), ", ")); $(decend(args[1])) \\right)" else _arg = "\\left( " * join(decend.(args), ", ") * " \\right)" @@ -105,7 +105,7 @@ const MATCHING_FUNCTIONS = [ function _indexing(x, prevop, config) if head(x) == :ref if getconfig(:index) == :subscript - return "$(operation(x))_{$(join(arguments(x), ", "))}" + return "$(operation(x))_{$(join(arguments(x), ","))}" elseif getconfig(:index) == :bracket argstring = join(decend.(arguments(x)), ", ") return "$(decend(operation(x)))\\left[$argstring\\right]" @@ -173,6 +173,9 @@ const MATCHING_FUNCTIONS = [ return str end end, + function _char(c, args...) + c isa Char ? string(c) : nothing + end, function array(x, args...) x isa AbstractArray ? _latexarray(x) : nothing end, @@ -218,6 +221,9 @@ const MATCHING_FUNCTIONS = [ end end end, + function _pass_through_LaTeXString(str, args...) + str isa LaTeXString ? str.s : nothing + end, function _tuple_expr(expr, prevop, config) head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing end, diff --git a/test/cdot_test.jl b/test/cdot_test.jl index f3a48263..3348dd29 100644 --- a/test/cdot_test.jl +++ b/test/cdot_test.jl @@ -4,29 +4,29 @@ using Test #inline -@test latexify(:(x * y); env=:inline, cdot=false) == raw"$x y$" +@test latexify(:(x * y); env=:inline, mulsym=" ") == raw"$x y$" -@test latexify(:(x * y); env=:inline, cdot=true) == raw"$x \cdot y$" +@test latexify(:(x * y); env=:inline, mulsym=" \\cdot ") == raw"$x \cdot y$" -@test latexify(:(x*(y+z)*y*(z+a)*(z+b)); env=:inline, cdot=false) == +@test latexify(:(x*(y+z)*y*(z+a)*(z+b)); env=:inline, mulsym=" ") == raw"$x \left( y + z \right) y \left( z + a \right) \left( z + b \right)$" -@test latexify(:(x*(y+z)*y*(z+a)*(z+b)); env=:inline, cdot=true) == +@test latexify(:(x*(y+z)*y*(z+a)*(z+b)); env=:inline, mulsym=" \\cdot ") == raw"$x \cdot \left( y + z \right) \cdot y \cdot \left( z + a \right) \cdot \left( z + b \right)$" # raw -@test latexify(:(x * y); env=:raw, cdot=false) == raw"x y" +@test latexify(:(x * y); env=:raw, mulsym=" ") == raw"x y" -@test latexify(:(x * y); env=:raw, cdot=true) == raw"x \cdot y" +@test latexify(:(x * y); env=:raw, mulsym=" \\cdot ") == raw"x \cdot y" -@test latexify(:(x * (y + z) * y * (z + a) * (z + b)); env=:raw, cdot=false) == +@test latexify(:(x * (y + z) * y * (z + a) * (z + b)); env=:raw, mulsym=" ") == raw"x \left( y + z \right) y \left( z + a \right) \left( z + b \right)" -@test latexify(:(x * (y + z) * y * (z + a) * (z + b)); env=:raw, cdot=true) == +@test latexify(:(x * (y + z) * y * (z + a) * (z + b)); env=:raw, mulsym=" \\cdot ") == raw"x \cdot \left( y + z \right) \cdot y \cdot \left( z + a \right) \cdot \left( z + b \right)" # array -@test latexify( [:(x*y), :(x*(y+z)*y*(z+a)*(z+b))]; env=:equation, transpose=true, cdot=false) == replace( +@test latexify( [:(x*y), :(x*(y+z)*y*(z+a)*(z+b))]; env=:equation, transpose=true, mulsym=" ") == replace( raw"\begin{equation} \left[ \begin{array}{cc} @@ -36,7 +36,7 @@ x y & x \left( y + z \right) y \left( z + a \right) \left( z + b \right) \\ \end{equation} ", "\r\n"=>"\n") -@test latexify( [:(x*y), :(x*(y+z)*y*(z+a)*(z+b))]; env=:equation, transpose=true, cdot=true) == replace( +@test latexify( [:(x*y), :(x*(y+z)*y*(z+a)*(z+b))]; env=:equation, transpose=true, mulsym=" \\cdot ") == replace( raw"\begin{equation} \left[ \begin{array}{cc} @@ -52,7 +52,7 @@ x \cdot y & x \cdot \left( y + z \right) \cdot y \cdot \left( z + a \right) \cdo # mdtable arr = ["x*(y-1)", 1.0, 3*2, :(x-2y), :symb] -@test latexify(arr; env=:mdtable, cdot=false) == +@test latexify(arr; env=:mdtable, mulsym=" ") == Markdown.md"| $x \left( y - 1 \right)$ | | ------------------------:| | $1.0$ | @@ -61,7 +61,7 @@ Markdown.md"| $x \left( y - 1 \right)$ | | $symb$ | " -@test latexify(arr; env=:mdtable, cdot=true) == +@test latexify(arr; env=:mdtable, mulsym=" \\cdot ") == Markdown.md"| $x \cdot \left( y - 1 \right)$ | | ------------------------------:| | $1.0$ | diff --git a/test/latexify_test.jl b/test/latexify_test.jl index 36f21084..1701fdbc 100644 --- a/test/latexify_test.jl +++ b/test/latexify_test.jl @@ -11,19 +11,18 @@ test_array = ["x/y * d" :x ; :( (t_sub_sub - x)^(2*p) ) 3//4 ] @test latexify("x * y") == raw"$x \cdot y$" -set_default(cdot = false) - +set_default(mulsym = " ") @test latexify("x * y") == raw"$x y$" -@test get_default() == Dict{Symbol,Any}(:cdot => false) +@test get_default() == Dict{Symbol,Any}(:mulsym => " ") -set_default(cdot = true, transpose = true) +set_default(mulsym = " \\cdot ", transpose = true) -@test get_default() == Dict{Symbol,Any}(:cdot => true,:transpose => true) -@test get_default(:cdot) == true -@test get_default(:cdot, :transpose) == (true, true) -@test get_default([:cdot, :transpose]) == Bool[1, 1] +@test get_default() == Dict{Symbol,Any}(:mulsym => " \\cdot ",:transpose => true) +@test get_default(:mulsym) == " \\cdot " +@test get_default(:mulsym, :transpose) == (" \\cdot ", true) +@test get_default([:mulsym, :transpose]) == [" \\cdot ", true] reset_default() @test get_default() == Dict{Symbol,Any}() diff --git a/test/macros.jl b/test/macros.jl index c66a8c16..c285fac9 100644 --- a/test/macros.jl +++ b/test/macros.jl @@ -1,18 +1,18 @@ l = @latexify dummyfunc(x; y=1, z=3) = x^2/y + z -@test l == raw"$\mathrm{dummyfunc}\left( x; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" +@test l == raw"$dummyfunc\left( x; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" @test_throws UndefVarError dummyfunc(1.) l2 = @latexrun dummyfunc2(x; y=1, z=3) = x^2/y + z -@test l2 == raw"$\mathrm{dummyfunc2}\left( x; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" +@test l2 == raw"$dummyfunc2\left( x; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" @test dummyfunc2(1.) == 4 l3 = @latexify dummyfunc2(x::Number; y=1, z=3) = x^2/y + z -@test l3 == raw"$\mathrm{dummyfunc2}\left( x::Number; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" +@test l3 == raw"$dummyfunc2\left( x::Number; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" l4 = @latexify dummyfunc2(::Number; y=1, z=3) = x^2/y + z -@test l4 == raw"$\mathrm{dummyfunc2}\left( ::Number; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" +@test l4 == raw"$dummyfunc2\left( ::Number; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" diff --git a/test/runtests.jl b/test/runtests.jl index 070a6cdb..3706efe6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,7 @@ using Test # Run tests @testset "macro test" begin include("macros.jl") end -@testset "recipe test" begin include("recipe_test.jl") end +# @testset "recipe test" begin include("recipe_test.jl") end @testset "latexify tests" begin include("latexify_test.jl") end @testset "latexraw tests" begin include("latexraw_test.jl") end @testset "latexalign tests" begin include("latexalign_test.jl") end @@ -19,7 +19,7 @@ using Test @testset "latextabular tests" begin include("latextabular_test.jl") end @testset "mdtable tests" begin include("mdtable_test.jl") end @testset "DataFrame Plugin" begin include("plugins/DataFrames.jl") end -@testset "unocode2latex" begin include("unicode2latex.jl") end +@testset "unicode2latex" begin include("unicode2latex.jl") end @testset "cdot test" begin include("cdot_test.jl") end @testset "numberformatters" begin include("numberformatters_test.jl") end @testset "utils test" begin include("utils_test.jl") end diff --git a/test/utils_test.jl b/test/utils_test.jl index 8a4aec46..a0ccb445 100644 --- a/test/utils_test.jl +++ b/test/utils_test.jl @@ -29,32 +29,34 @@ Latexify._writetex(xdoty_tex; name=name) tex = open("$(name).tex") do f read(f, String) end -@test tex == replace(raw""" -\documentclass[varwidth=100cm]{standalone} +@test tex == replace( +raw"\documentclass[varwidth=100cm]{standalone} \usepackage{amssymb} \usepackage{amsmath} + \begin{document} { \Large $x \cdot y$ } \end{document} -""", "\r\n"=>"\n") +", "\r\n"=>"\n") Latexify._writetex(L"\ce{ 2 P_1 &<=>[k_{+}][k_{-}] D_{1}}"; name=name) tex = open("$(name).tex") do f read(f, String) end -@test tex == replace(raw""" -\documentclass[varwidth=100cm]{standalone} +@test tex == replace( +raw"\documentclass[varwidth=100cm]{standalone} \usepackage{amssymb} \usepackage{amsmath} -\usepackage{mhchem} +\usepackage[version=3]{mhchem} + \begin{document} { \Large $\ce{ 2 P_1 &<=>[k_{+}][k_{-}] D_{1}}$ } \end{document} -""", "\r\n"=>"\n") +", "\r\n"=>"\n") \ No newline at end of file From 9ae35a6942a4c7303bfd18d1865dcd5eb2388a06 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 9 Apr 2021 17:09:26 +0200 Subject: [PATCH 20/40] remove broken latexoperation import --- src/Latexify.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Latexify.jl b/src/Latexify.jl index 88c2054b..b6d704da 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -29,7 +29,6 @@ end include("unicode2latex.jl") include("function2latex.jl") include("latexraw.jl") -include("latexoperation.jl") include("latexarray.jl") include("latexalign.jl") include("latexbracket.jl") From 068a37ee42f09570c0d82642a7e5839163280f5d Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 9 Apr 2021 17:30:51 +0200 Subject: [PATCH 21/40] Stop testing on Julia 0.7. --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a42ad77c..6427bbc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,6 @@ jobs: fail-fast: false matrix: version: - - '0.7' - '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia. - 'nightly' os: From 4a5c03e4f744645f408066952c04941942e8921f Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 9 Apr 2021 17:33:38 +0200 Subject: [PATCH 22/40] Test older Julia versions too. --- .github/workflows/ci.yml | 1 + src/latexraw.jl | 2 +- src/latexraw_recipes.jl | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6427bbc8..f8dd3252 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: fail-fast: false matrix: version: + - '0.7' - '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia. - 'nightly' os: diff --git a/src/latexraw.jl b/src/latexraw.jl index ce4f8bcb..98216c62 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -34,7 +34,7 @@ unpack(x) = (head(x), operation(x), arguments(x)) function decend(e, prevop=Val(:_nothing))::String for f in MATCHING_FUNCTIONS[end:-1:1] call_result = f(e, prevop, CONFIG) - if !isnothing(call_result) + if !(call_result === nothing) return call_result break end diff --git a/src/latexraw_recipes.jl b/src/latexraw_recipes.jl index 572b6df2..ed284302 100644 --- a/src/latexraw_recipes.jl +++ b/src/latexraw_recipes.jl @@ -163,7 +163,7 @@ const MATCHING_FUNCTIONS = [ end end, function _nothing(x, prevop, config) - isnothing(x) ? "" : nothing + x === nothing ? "" : nothing end, function symbol(sym, _, config) if sym isa Symbol From da056d68b0671318edd7e3a0bb9316a11374ca8a Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sat, 10 Apr 2021 13:52:25 +0200 Subject: [PATCH 23/40] Remove old refactor prototypes. --- refactor_prototype.jl | 98 ----------------------------------------- refactor_prototype_3.jl | 77 -------------------------------- 2 files changed, 175 deletions(-) delete mode 100644 refactor_prototype.jl delete mode 100644 refactor_prototype_3.jl diff --git a/refactor_prototype.jl b/refactor_prototype.jl deleted file mode 100644 index a255b975..00000000 --- a/refactor_prototype.jl +++ /dev/null @@ -1,98 +0,0 @@ -using Latexify -using LaTeXStrings - -nested(::Any) = false -arguments(::Any) = nothing -operation(::Any) = nothing -head(::Any) = nothing - -nested(::Expr) = true -arguments(ex::Expr) = ex.args[2:end] -operation(ex::Expr) = ex.args[1] -head(ex::Expr) = ex.head - -### Ninja functions -value(::Val{T}) where T = T -isiterable(x) = hasmethod(iterate, (typeof(x),)) -ValUnion(x) = isiterable(x) ? Union{typeof.(Val.(x))...} : Val{x} -ValUnion(a, b, c...) = ValUnion((a, b, c...)) - -joinsafe(x, y) = isiterable(x) ? join(x, y) : string(x) - -### Overloadable formatting functions -surround(x) = "\\left( $x \\right)" - -#### Automatic argument conversion for convenience - -# Reached the end of the tree? -lf(ex, prevop=nothing) = nested(ex) ? lf(head(ex), operation(ex), prevop, arguments(ex)) : string(ex) - -# Let three-argument with omitted head imply that head == :call. -# This reduces verbosity of a bunch of calls. -lf(::Val{:call}, op, prevop, args) = lf(op, prevop, args) - -lf(head::Symbol, op::Symbol, prevop::Symbol, args) = lf(Val{head}(), Val{op}(), Val{prevop}(), args) -# Allows prevopts like `nothing` or just passing through `Val`'s -lf(head::Symbol, op::Symbol, prevop, args) = lf(Val{head}(), Val{op}(), prevop, args) - -#### :call functions -# Fallback method for functions of type f(x...) -lf(op, ::Any, args) = "$(value(op))\\left($(join(lf.(args, op), ", "))\\right)" - -function lf(op::ValUnion(Latexify.trigonometric_functions), ::Any, args) - fstr = get(Latexify.function2latex, value(op), "\\$(value(op))") - return "$fstr\\left($(join(lf.(args, op), ", "))\\right)" -end - -lf(op::Val{:+}, prevop, args) = join(lf.(args, op), " + ") -function lf(op::Val{:+}, ::ValUnion(:*, :^), args) - surround(join(lf.(args, op), " $(value(op)) ")) -end - -lf(op::ValUnion(:-, :.-), prevop, args; kw...) = - length(args) == 1 ? "- $(lf(args[1], op))" : join(lf.(args, op), " - ") - -lf(op::ValUnion(:*, :.*), prevop, args; mul_symb=" \\cdot ", kw...) = - join(lf.(args, op), string(mul_symb)) - -lf(op::ValUnion(:/, :./), prevop, args; kw...) = - "\\frac{$(lf(args[1], op))}{$(lf(args[2], op))}" - -function lf(op::ValUnion(:^, :.^), ::Any, args; kw...) - if operation(args[1]) in Latexify.trigonometric_functions - fsym = args[1] - fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") - "$fstring^{$(lf(args[2], op))}\\left( $(join(lf.(arguments(args[1]), operation(args[1])), ", ")) \\right)" - else - "$(lf(args[1], op))^{$(lf(args[2], Val{:NoSurround}()))}" - end -end - -lf(op::ValUnion(:±, :.±), prevop, args) = "$(args[1]) \\pm $(lf(args[2], op))" - -# ### non :call functions -lf(::Val{:ref}, op, prevop, args; kw...) = "$(value(op))\\left[$(join(lf.(args, op), ", "))\\right]" -lf(::Val{:(=)}, op, prevop, args) = "$(lf(value(op), Val{:NoSurround}())) = $(lf(args[1], Val{:NoSurround}()))" - -##### Latexify special commands -lf(::Val{:showargs}, prevop, args; kw...) = string(args) -lf(::Val{:showprevop}, prevop, args; kw...) = string(prevop) -function lf(::Val{:textcolor}, prevop, args) - if length(args) == 2 - return "\\textcolor{$(head(args[1]) == :tuple ? join(args[1].args, ",") : args[1])}{$(lf(args[2], prevop))}" - elseif length(args) == 3 - return "\\textcolor[$(joinsafe(args[1], ","))]{$(head(args[2]) == :tuple ? join(args[2].args, ",") : args[2])}{$(lf(args[3], prevop))}" - else - error(ArgumentError("Latexify's textcolor takes two or three arguments.")) - end -end -lf(::Val{:mathrm}, prevop, args) = "\\mathrm{$(lf(args[1], prevop))}" -lf(::Val{:merge}, prevop, args; kw...) = join(lf.(args, prevop), "") - - -function latexdive(x) - str = lf(x) - return LaTeXString(str) -end - - diff --git a/refactor_prototype_3.jl b/refactor_prototype_3.jl deleted file mode 100644 index d20a6768..00000000 --- a/refactor_prototype_3.jl +++ /dev/null @@ -1,77 +0,0 @@ -const MATCHING_FUNCTIONS = Function[] - -add_matcher(f) = push!(MATCHING_FUNCTIONS, f) - -_check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op - -# User would write: -# text_color_latexify(e) = "\\textcolor{$(args[1])}{$(latexify(args[2]))}" -# add_call_matcher(text_color_latexify, :textcolor) -function add_call_matcher(op::Symbol, f::Function) - push!(MATCHING_FUNCTIONS, (e, p) -> _check_call_match(e, op) ? f(e, p) : nothing) -end - - -nested(::Any) = false -arguments(::Any) = nothing -operation(::Any) = nothing -head(::Any) = nothing - -nested(::Expr) = true -arguments(ex::Expr) = ex.args[2:end] -operation(ex::Expr) = ex.args[1] -head(ex::Expr) = ex.head - - -surround(x) = "\\left( $x \\right)" - -function dive(x, prevop=nothing) - args = map(arg -> nested(arg) ? dive(arg, operation(x)) : arg, arguments(x)) -# latexterminal(head(x), operation(x), args, prevop) - for f in MATCHING_FUNCTIONS[end:-1:1] - if f(head(x), operation(x), args, prevop) !== nothing - return f(head(x), operation(x), args, prevop) - break - end - end - return "Caught nothing!!" -end - - -if false -func(x, prevop) = string(x) -# func(x::Expr, prevop::Symbol) = func(x, Val{prevop}()) -function func(e::Expr, prevop=Val(:_nothing)) - for f in MATCHING_FUNCTIONS[end:-1:1] - if f(e, prevop) !== nothing - return f(e, prevop) - break - end - end - return "Caught nothing!!" -end - - -### Overloadable formatting functions - -# match_equals(e) = (e isa Expr && e.head === :=) ? - # "$(latexify(value(lhs_val))) = $(latexify(first(rhs_array)))" : - # nothing - - - -match_addition(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :+) ? join(func.(e.args[2:end], e.args[1]), " + ") : nothing -match_addition(e, prevop::Val{:*}) = (e isa Expr && e.head==:call && e.args[1] == :+) ? surround(join(func.(e.args[2:end], e.args[1]), " + ")) : nothing - -match_division(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :/) ? "\\frac{$(func(e.args[2], e.args[1]))}{$(func(e.args[3], e.args[1]))}" : nothing - -match_mul(e, prevop) = (e isa Expr && e.head==:call && e.args[1] == :*) ? join(func.(e.args[2:end], Val{:*}()), " \\cdot ") : nothing - -struct Operation{T} - head::Symbol - op::Symbol - args::T - prevop::Union{Symbol, Nothing} -end -Operation(ex::Expr, prevop) = Operation(ex.head, ex.args[1], ex.args[2:end], prevop) -end \ No newline at end of file From 1d86347bff871e18852427a618108a0b6651fe12 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sat, 10 Apr 2021 15:17:28 +0200 Subject: [PATCH 24/40] Fix LaTeXString substring passthrough. --- src/latexraw_recipes.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/latexraw_recipes.jl b/src/latexraw_recipes.jl index ed284302..c2b372ad 100644 --- a/src/latexraw_recipes.jl +++ b/src/latexraw_recipes.jl @@ -221,8 +221,11 @@ const MATCHING_FUNCTIONS = [ end end end, + function _pass_through_LaTeXString_substrings(str, args...) + str isa SubString{LaTeXString} ? String(str) : nothing + end, function _pass_through_LaTeXString(str, args...) - str isa LaTeXString ? str.s : nothing + str isa LaTeXString ? str.s : nothing end, function _tuple_expr(expr, prevop, config) head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing From 267308c6487b782ab6acecefafc729f33f80b88e Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Sun, 11 Apr 2021 17:05:34 +0200 Subject: [PATCH 25/40] Replace latexify top function. --- src/latexify_function.jl | 103 ++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/src/latexify_function.jl b/src/latexify_function.jl index 44b966c7..2881865b 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -1,27 +1,34 @@ -function latexify(args...; kwargs...) - +function latexify(args...; env=:auto, kwargs...) ## Let potential recipes transform the arguments. - args, kwargs = apply_recipe(args...; default_kwargs..., kwargs...) + # kwargs = merge(default_kwargs, kwargs) empty!(CONFIG) - merge!(CONFIG, DEFAULT_CONFIG, kwargs) - ## If the environment is unspecified, use auto inference. - env = get(kwargs, :env, :auto) - - latex_function = infer_output(env, args...) - - result = latex_function(args...; kwargs...) + merge!(CONFIG, DEFAULT_CONFIG, default_kwargs, kwargs) + + if env==:auto + call_result = iterate_top_matcher(args, CONFIG) + else + func = OUTPUTFUNCTIONS[env] + call_result = func(args...; CONFIG...) + end + COPY_TO_CLIPBOARD && clipboard(call_result) + AUTO_DISPLAY && display(call_result) + get(CONFIG, :render, false) && render(call_result) + return call_result +end - COPY_TO_CLIPBOARD && clipboard(result) - AUTO_DISPLAY && display(result) - get(kwargs, :render, false) && render(result) - return result +function iterate_top_matcher(args, kwargs) + for f in TOP_LEVEL_MATCHERS[end:-1:1] + call_result = f(args, kwargs) + if !(call_result === nothing) + return call_result + end + end + throw(ArgumentError("No top-level matching expression for \a$args \n$kwargs")) end apply_recipe(args...; kwargs...) = (args, kwargs) -# These functions should only be called from inside `latexify()`, so that -# `apply_recipe` gets a chance to change args const OUTPUTFUNCTIONS = Dict( :inline => _latexinline, :tabular => _latextabular, @@ -36,33 +43,39 @@ const OUTPUTFUNCTIONS = Dict( :mdtable => mdtable, :mdtext => mdtext, ) -function infer_output(env, args...) - env === :auto && return get_latex_function(args...) - # Must be like this, because items in OUTPUTFUNCTIONS must be defined - env in [:arrows, :chem, :chemical, :arrow] && return _chemical_arrows - return OUTPUTFUNCTIONS[env] -end - -""" - get_latex_function(args...) - -Use overloading to determine which latex environment to output. - -This determines the default behaviour of `latexify()` for different inputs. -""" -get_latex_function(args...) = _latexinline -get_latex_function(args::AbstractArray...) = _latexequation -get_latex_function(args::AbstractDict) = (args...; kwargs...) -> _latexequation(_latexarray(args...; kwargs...); kwargs...) -get_latex_function(args::Tuple...) = (args...; kwargs...) -> _latexequation(_latexarray(args...; kwargs...); kwargs...) -get_latex_function(arg::LaTeXString) = (arg; kwargs...) -> arg - -function get_latex_function(x::AbstractArray{T}) where T <: AbstractArray - try - x = reduce(hcat, x) - return (args...; kwargs...) -> _latexequation(_latexarray(args...; kwargs...); kwargs...) - catch - return _latexinline - end -end -get_latex_function(lhs::AbstractVector, rhs::AbstractVector) = _latexalign +const TOP_LEVEL_MATCHERS = [ + function _inline_fallback(args, kwargs) + return latexstring(latexraw(args...; kwargs...)) + end, + function _equation_array(args, kwargs) + if eltype(args) <: AbstractArray || eltype(args) <: Tuple + return _latexequation(args...; kwargs...) + end + end, + function _align(args, kwargs) + if length(args) == 2 && (eltype(args) <: AbstractVector) + return _latexalign(args...; kwargs...) + end + end, + function _dicts(args, kwargs) + if length(args) == 1 && (args[1] isa AbstractDict || args[1] isa NamedTuple) + _latexalign(collect(keys(args[1])), collect(values(args[1])); kwargs...) + end + end, + function _equation(args, kwargs) + if length(args) == 1 && args[1] isa AbstractArray && eltype(args[1]) <: AbstractArray + try + x = reduce(hcat, args[1]) + return _latexequation(args...; kwargs...) + catch + return _latexinline(args...; kwargs...) + end + end + end, + function _latexstring_passthrough(args, kwargs) + if length(args) == 1 && args[1] isa LaTeXString + return args[1] + end + end, +] \ No newline at end of file From 056ba0caf7d06e8cab693fa793665aad70d5853c Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Mon, 12 Apr 2021 16:46:12 +0200 Subject: [PATCH 26/40] Stop recursing to top-level function. --- src/latexalign.jl | 88 ++++++++++++++++++++++---------------------- src/latexarray.jl | 3 +- src/latexbracket.jl | 2 +- src/latexequation.jl | 2 +- src/latexinline.jl | 2 +- src/latextabular.jl | 2 +- 6 files changed, 51 insertions(+), 48 deletions(-) diff --git a/src/latexalign.jl b/src/latexalign.jl index ec5c813d..a695ecaf 100644 --- a/src/latexalign.jl +++ b/src/latexalign.jl @@ -1,47 +1,49 @@ -@doc doc""" - latexalign() -Generate a ``LaTeX`` align environment from an input. - -# Examples -## use with arrays - -```julia -lhs = [:(dx/dt), :(dy/dt), :(dz/dt)] -rhs = [:(y-x), :(x*z-y), :(-z)] -latexalign(lhs, rhs) -``` - -```LaTeX -\begin{align} -\frac{dx}{dt} =& y - x \\\\ -\frac{dy}{dt} =& x \cdot z - y \\\\ -\frac{dz}{dt} =& - z \\\\ -\end{align} -``` - -## use with ParameterizedFunction - -```julia-repl -julia> using DifferentialEquations -julia> ode = @ode_def foldChangeDetection begin - dm = r_m * (i - m) - dy = r_y * (p_y * i/m - y) -end i r_m r_y p_y - -julia> latexalign(ode) -``` -```LaTeX -\begin{align} -\frac{dm}{dt} =& r_{m} \cdot \left( i - m \right) \\\\ -\frac{dy}{dt} =& r_{y} \cdot \left( \frac{p_{y} \cdot i}{m} - y \right) \\\\ -\end{align} -``` - -""" -latexalign(args...; kwargs...) = latexify(args...; kwargs..., env=:align) - -function _latexalign(arr::AbstractMatrix; separator=" =& ", double_linebreak=false, starred=false, rows=:all, aligned=false, kwargs...) +# @doc doc""" +# latexalign() +# Generate a ``LaTeX`` align environment from an input. + +# # Examples +# ## use with arrays + +# ```julia +# lhs = [:(dx/dt), :(dy/dt), :(dz/dt)] +# rhs = [:(y-x), :(x*z-y), :(-z)] +# latexalign(lhs, rhs) +# ``` + +# ```LaTeX +# \begin{align} +# \frac{dx}{dt} =& y - x \\\\ +# \frac{dy}{dt} =& x \cdot z - y \\\\ +# \frac{dz}{dt} =& - z \\\\ +# \end{align} +# ``` + +# ## use with ParameterizedFunction + +# ```julia-repl +# julia> using DifferentialEquations +# julia> ode = @ode_def foldChangeDetection begin +# dm = r_m * (i - m) +# dy = r_y * (p_y * i/m - y) +# end i r_m r_y p_y + +# julia> latexalign(ode) +# ``` +# ```LaTeX +# \begin{align} +# \frac{dm}{dt} =& r_{m} \cdot \left( i - m \right) \\\\ +# \frac{dy}{dt} =& r_{y} \cdot \left( \frac{p_{y} \cdot i}{m} - y \right) \\\\ +# \end{align} +# ``` + +# """ +# latexalign(args...; kwargs...) = latexify(args...; kwargs..., env=:align) +latexalign(args...; kwargs...) = _latexalign(args...; kwargs...) + +# function _latexalign(arr::AbstractMatrix; separator=" =& ", double_linebreak=false, starred=false, rows=:all, aligned=false, kwargs...) +function _latexalign(arr; separator=" =& ", double_linebreak=false, starred=false, rows=:all, aligned=false, kwargs...) eol = double_linebreak ? " \\\\\\\\\n" : " \\\\\n" arr = latexraw.(arr; kwargs...) separator isa String && (separator = fill(separator, size(arr)[1])) diff --git a/src/latexarray.jl b/src/latexarray.jl index 53d94e60..f12dbc76 100644 --- a/src/latexarray.jl +++ b/src/latexarray.jl @@ -1,4 +1,5 @@ -latexarray(args...; kwargs...) = latexify(args...;kwargs...,env=:array) +# latexarray(args...; kwargs...) = latexify(args...;kwargs...,env=:array) +latexarray(args...; kwargs...) = _latexarray(args...;kwargs...) function _latexarray(arr::AbstractArray) adjustment = getconfig(:adjustment) diff --git a/src/latexbracket.jl b/src/latexbracket.jl index 18084a0d..9465f256 100644 --- a/src/latexbracket.jl +++ b/src/latexbracket.jl @@ -1,4 +1,4 @@ -latexbracket(args...; kwargs...) = latexify(args...; kwargs..., env=:bracket) +latexbracket(args...; kwargs...) = _latexbracket(args...; kwargs...) function _latexbracket(x; kwargs...) latexstr = LaTeXString( "\\[\n" * latexraw(x; kwargs...) * "\\]\n") diff --git a/src/latexequation.jl b/src/latexequation.jl index c772c885..569fbd8b 100644 --- a/src/latexequation.jl +++ b/src/latexequation.jl @@ -1,5 +1,5 @@ -latexequation(args...; kwargs...) = latexify(args...; kwargs..., env=:equation) +latexequation(args...; kwargs...) = _latexequation(args...; kwargs...) function _latexequation(eq; starred=false, kwargs...) latexstr = latexraw(eq; kwargs...) diff --git a/src/latexinline.jl b/src/latexinline.jl index 9991b6be..fe2a1083 100644 --- a/src/latexinline.jl +++ b/src/latexinline.jl @@ -1,4 +1,4 @@ -latexinline(args...;kwargs...) = latexify(args...;kwargs...,env=:inline) +latexinline(args...;kwargs...) = _latexinline(args...;kwargs...) function _latexinline(x; kwargs...) latexstr = latexstring( latexify(x; kwargs...,env=:raw) ) diff --git a/src/latextabular.jl b/src/latextabular.jl index 816d6ff7..4714c2ef 100644 --- a/src/latextabular.jl +++ b/src/latextabular.jl @@ -1,4 +1,4 @@ -latextabular(args...; kwargs...) = latexify(args...; kwargs..., env=:tabular) +latextabular(args...; kwargs...) = _latextabular(args...; kwargs...) function _latextabular(arr::AbstractMatrix; latex::Bool=true, booktabs::Bool=false, head=[], side=[], adjustment::Symbol=:c, transpose=false, kwargs...) transpose && (arr = permutedims(arr, [2,1])) From d59f5b55940582fa1741fb3cc7558b18d9f089cd Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Mon, 12 Apr 2021 16:47:52 +0200 Subject: [PATCH 27/40] Add IOStream prototype. --- src/Latexify.jl | 3 +- src/latexraw.jl | 24 ++- src/matching_functions_test.jl | 368 +++++++++++++++++++++++++++++++++ 3 files changed, 387 insertions(+), 8 deletions(-) create mode 100644 src/matching_functions_test.jl diff --git a/src/Latexify.jl b/src/Latexify.jl index b6d704da..dd3f07b2 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -38,6 +38,7 @@ include("latextabular.jl") include("default_kwargs.jl") include("recipes.jl") include("macros.jl") +include("matching_functions_test.jl") include("mdtable.jl") include("mdtext.jl") @@ -114,7 +115,7 @@ macro append_test!(fname, str) end # const _ex = :(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y)) -latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) +# latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) # Latexify._latextree(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) # latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) # latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) diff --git a/src/latexraw.jl b/src/latexraw.jl index 98216c62..71ed6b15 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -1,8 +1,10 @@ function latexraw(expr; kwargs...) empty!(CONFIG) merge!(CONFIG, DEFAULT_CONFIG, kwargs) - str = decend(expr) - CONFIG[:convert_unicode] && (str = unicode2latex(str)) + io = IOBuffer(; append=true) + decend(io, expr) + str = String(take!(io)) +# CONFIG[:convert_unicode] && (str = unicode2latex(str)) return LaTeXString(str) end @@ -31,15 +33,23 @@ head(ex::Expr) = ex.head unpack(x) = (head(x), operation(x), arguments(x)) -function decend(e, prevop=Val(:_nothing))::String - for f in MATCHING_FUNCTIONS[end:-1:1] - call_result = f(e, prevop, CONFIG) +function decend(io::IO, e, prevop=Val(:_nothing))::Nothing + for f in MATCHING_FUNCTIONS_TEST[end:-1:1] + call_result = f(io, e, prevop, CONFIG) if !(call_result === nothing) - return call_result + return nothing break end end throw(ArgumentError("No matching expression conversion function for $e")) end -surround(x) = "\\left( $x \\right)" \ No newline at end of file +surround(x) = "\\left( $x \\right)" + +function join_decend(io::IO, args, delim; prevop=nothing) + for arg in args[1:end-1] + decend(io, arg, prevop) + write(io, delim) + end + decend(io, args[end], prevop) +end \ No newline at end of file diff --git a/src/matching_functions_test.jl b/src/matching_functions_test.jl new file mode 100644 index 00000000..7d4d4f58 --- /dev/null +++ b/src/matching_functions_test.jl @@ -0,0 +1,368 @@ + +const MATCHING_FUNCTIONS_TEST = [ + function report_bad_call(io::IO, expr, prevop, config) + println(""" + Unsupported input with + expr=$expr + prevop=$prevop + and config=$config + """) + return nothing + end, + function call(io::IO, expr, prevop, config) + if expr isa Expr && head(expr) == :call + h, op, args = unpack(expr) + + if op isa Symbol + funcname = string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) + write(io, funcname) + else + decend(io, op) + end + if length(args) >= 1 && head(args[1]) == :parameters + write(io, "\\left( ") + join_decend(io, args[2:end], ", ") + write(io, "; ") + decend(io, args[1]) + write(io, " \\right)") + # _arg = "\\left( $(join(decend.(args[2:end]), ", ")); $(decend(args[1])) \\right)" + else + write(io, "\\left( ") + join_decend(io, args, ", ") + write(io, " \\right)") + # _arg = "\\left( " * join(decend.(args), ", ") * " \\right)" + end + return true + end + end, + # function _sqrt(io::IO, ex, prevop, config) + # operation(ex) == :sqrt ? "\\$(operation(ex)){$(arguments(ex)[1])}" : nothing + # end, + # function _abs(io::IO, ex, prevop, config) + # operation(ex) == :abs ? "\\left\\|$(arguments(ex)[1])\\right\\|" : nothing + # end, + # function _single_comparison(io::IO, ex, args...) + # if operation(ex) ∈ keys(comparison_operators) && length(arguments(ex)) == 2 + # str = "$(arguments(ex)[1]) $(comparison_operators[operation(ex)]) $(arguments(ex)[2])" + # str = "\\left( $str \\right)" + # return str + # end + # end, + # function _if(io::IO, ex, prevop, config) + # if ex isa Expr && head(ex) == :if + # str = build_if_else_body( + # ex.args, + # getconfig(:ifstr), + # getconfig(:elseifstr), + # getconfig(:elsestr) + # ) + # return """ + # \\begin{cases} + # $str + # \\end{cases}""" + # end + # end, + # function _elseif(io::IO, ex, prevop, config) + # if ex isa Expr && head(ex) == :elseif + # str = build_if_else_body( + # ex.args, + # getconfig(:elseifstr), + # getconfig(:elseifstr), + # getconfig(:elsestr) + # ) + # end + # end, + # function _oneline_function(io::IO, ex, prevop, config) + # if head(ex) == :function && length(arguments(ex)) == 1 + # return "$(decend(operation(ex), head(ex))) = $(decend(arguments(ex)[1], head(ex)))" + # end + # end, + # function _return(io::IO, ex, prevop, config) + # head(ex) == :return && length(arguments(ex)) == 0 ? decend(operation(ex)) : nothing + # end, + # function _chained_comparisons(io::IO, ex, _...) + # if head(ex) == :comparison && Symbol.(arguments(ex)[1:2:end]) ⊆ keys(comparison_operators) + # str = join([isodd(i) ? "$var" : comparison_operators[Symbol(var)] for (i, var) in enumerate(decend.(vcat(operation(ex), arguments(ex))))], " ") + # str = "\\left( $str \\right)" + # return str + # end + # end, + # function _type_annotation(io::IO, ex, prevop, config) + # if head(ex) == :(::) + # if length(arguments(ex)) == 0 + # return "::$(operation(ex))" + # elseif length(arguments(ex)) == 1 + # return "$(operation(ex))::$(arguments(ex)[1])" + # end + # end + # end, + # function _wedge(io::IO, ex, prevop, config) + # head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\wedge $(decend(arguments(ex)[1]))" : nothing + # end, + # function _vee(io::IO, ex, prevop, config) + # head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\vee $(decend(arguments(ex)[1]))" : nothing + # end, + # function _negation(io::IO, ex, prevop, config) + # operation(ex) == :(!) ? "\\neg $(arguments(ex)[1])" : nothing + # end, + # function _kw(io::IO, x, args...) + # head(x) == :kw ? "$(decend(operation(x))) = $(decend(arguments(x)[1]))" : nothing + # end, + # function _parameters(io::IO, x, args...) + # head(x) == :parameters ? join(decend.(vcat(operation(x), arguments(x))), ", ") : nothing + # end, + # function _indexing(io::IO, x, prevop, config) + # if head(x) == :ref + # if getconfig(:index) == :subscript + # return "$(operation(x))_{$(join(arguments(x), ","))}" + # elseif getconfig(:index) == :bracket + # argstring = join(decend.(arguments(x)), ", ") + # return "$(decend(operation(x)))\\left[$argstring\\right]" + # else + # throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket")) + # end + # end + # end, + # function _broadcast_macro(io::IO, ex, prevop, config) + # if head(ex) == :macrocall && operation(ex) == Symbol("@__dot__") + # return decend(arguments(ex)[end]) + # end + # end, + # function _block(io::IO, x, args...) + # if head(x) == :block + # return decend(vcat(operation(x), arguments(x))[end]) + # end + # end, + function number(io::IO, x, prevop, config) + if x isa Number + try isinf(x) && write(io, "\\infty") && return true; catch; end + fmt = getconfig(:fmt) + fmt isa String && (fmt = PrintfNumberFormatter(fmt)) + str = string(fmt(x)) + sign(x) == -1 && prevop == :^ && (str = surround(str)) + write(io, str) + return true + end + end, + # function rational_expr(io::IO, x, prevop, config) + # if operation(x) == :// + # if arguments(x)[2] == 1 + # return decend(arguments[1], prevop) + # else + # decend:($(arguments(x)[1])/$(arguments(x)[2])), prevop) + # end + # end + # end, + # function rational(io::IO, x, prevop, config) + # if x isa Rational + # str = x.den == 1 ? decend(x.num, prevop) : decend(:($(x.num)/$(x.den)), prevop) + # prevop ∈ [:*, :^] && (str = surround(str)) + # return str + # end + # end, + # function complex(io::IO, z, prevop, config) + # if z isa Complex + # str = "$(decend(z.re))$(z.im < 0 ? "-" : "+" )$(decend(abs(z.im)))\\textit{i}" + # prevop ∈ [:*, :^] && (str = surround(str)) + # return str + # end + # end, + # function _missing(io::IO, x, prevop, config) + # if ismissing(x) + # "\\textrm{NA}" + # end + # end, + # function _nothing(io::IO, x, prevop, config) + # x === nothing ? "" : nothing + # end, + function symbol(io::IO, sym, _, config) + if sym isa Symbol + str = string(sym == :Inf ? :∞ : sym) + str = convert_subscript(str) + getconfig(:convert_unicode) && (str = unicode2latex(str)) + write(io, str) + return true + end + end, + # function _char(io::IO, c, args...) + # c isa Char ? string(c) : nothing + # end, + # function array(io::IO, x, args...) + # x isa AbstractArray ? _latexarray(x) : nothing + # end, + # function tuple(io::IO, x, args...) + # x isa Tuple ? _latexarray(x) : nothing + # end, + # function vect_exp(io::IO, x, args...) + # head(x) ∈ [:vect, :vcat] ? _latexarray(expr_to_array(x)) : nothing + # end, + # function hcat_exp(io::IO, x, args...) + # # head(x)==:hcat ? _latexarray(permutedims(vcat(operation(x), arguments(x)))) : nothing + # head(x)==:hcat ? _latexarray(expr_to_array(x)) : nothing + # end, + # (expr, prevop, config) -> begin + # h, op, args = unpack(expr) + # # if (expr isa LatexifyOperation || h == :LatexifyOperation) && op == :merge + # if h == :call && op == :latexifymerge + # join(decend.(args), "") + # end + # end, + # function parse_string(io::IO, str, prevop, config) + # if str isa AbstractString + # try + # ex = Meta.parse(str) + # return decend(ex, prevop) + # catch ParseError + # error(""" + # in Latexify.jl: + # You are trying to create latex-maths from a `String` that cannot be parsed as + # an expression. + + # `latexify` will, by default, try to parse any string inputs into expressions + # and this parsing has just failed. + + # If you are passing strings that you want returned verbatim as part of your input, + # try making them `LaTeXString`s first. + + # If you are trying to make a table with plain text, try passing the keyword + # argument `latex=false`. You should also ensure that you have chosen an output + # environment that is capable of displaying not-maths objects. Try for example + # `env=:table` for a latex table or `env=:mdtable` for a markdown table. + # """) + # end + # end + # end, + # function _pass_through_LaTeXString_substrings(io::IO, str, args...) + # str isa SubString{LaTeXString} ? String(str) : nothing + # end, + # function _pass_through_LaTeXString(io::IO, str, args...) + # str isa LaTeXString ? str.s : nothing + # end, + # function _tuple_expr(io::IO, expr, prevop, config) + # head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing + # end, + # function strip_broadcast_dot(io::IO, expr, prevop, config) + # h, op, args = unpack(expr) + # if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') + # return string(decend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) + # end + # end, + # function strip_broadcast_dot_call(io::IO, expr, prevop, config) + # h, op, args = unpack(expr) + # if expr isa Expr && config[:strip_broadcast] && h == :. + # return decend(Expr(:call, op, args[1].args...), prevop) + # end + # end, + # function plusminus(io::IO, expr, prevop, config) + # h, op, args = unpack(expr) + # if h == :call && op == :± + # return "$(decend(args[1], op)) \\pm $(decend(args[2], op))" + # end + # end, + function division(io::IO, expr, prevop, config) + h, op, args = unpack(expr) + if h == :call && op == :/ + write(io, "\\frac{") + decend(io, args[1], op) + write(io, "}{") + decend(io, args[2], op) + write(io, "}") + # "\\frac{$(decend(args[1], op))}{$(decend(args[2], op))}" + return true + end + end, + # function multiplication(io::IO, expr, prevop, config) + # h, op, args = unpack(expr) + # if h == :call && op == :* + # join(decend.(args, op), "$(config[:mulsym])") + # end + # end, + function addition(io::IO, expr, prevop, config) + h, op, args = unpack(expr) + if h == :call && op == :+ + prevop ∈ [:*, :^] && write(io, "\\left( ") + str = join_decend(io, args, " + ") + # str = replace(str, "+ -"=>"-") + # prevop ∈ [:*, :^] && (str = surround(str)) + prevop ∈ [:*, :^] && write(io, " \\right)") + # write() + return true + end + end, + # function subtraction(io::IO, expr, prevop, config) + # # this one is so gnarly because it tries to fix stuff like - - or -(-(x-y)) + # # -(x) is also a bit different to -(x, y) which does not make things simpler + # h, op, args = unpack(expr) + # if h == :call && op == :- + # if length(args) == 1 + # if operation(args[1]) == :- && length(arguments(args[1])) == 1 + # return decend(arguments(args[1])[1], prevop) + # elseif args[1] isa Number && sign(args[1]) == -1 + # # return _latexraw(-args[1]; config...) + # return decend(-args[1], op) + # else + # _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] + # return prevop == :^ ? surround("$op$_arg") : "$op$_arg" + # end + + # elseif length(args) == 2 + # if args[2] isa Number && sign(args[2]) == -1 + # return "$(decend(args[1], :+)) + $(decend(-args[2], :+))" + # end + # if operation(args[2]) == :- && length(arguments(args[2])) == 1 + # return "$(decend(args[1], :+)) + $(decend(arguments(args[2])[1], :+))" + # end + # if operation(args[2]) ∈ [:-, :.-, :+, :.+] + # return "$(decend(args[1], op)) - $(surround(decend(args[2], op)))" + # end + # str = join(decend.(args, op), " - ") + # prevop ∈ [:*, :^] && (str = surround(str)) + # return str + # end + # end + # end, + # function pow(io::IO, expr, prevop, config) + # h, op, args = unpack(expr) + # if h == :call && op == :^ + # if operation(args[1]) in Latexify.trigonometric_functions + # fsym = operation(args[1]) + # fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") + # "$fstring^{$(decend(args[2], op))}\\left( $(join(decend.(arguments(args[1]), operation(args[1])), ", ")) \\right)" + # else + # "$(decend(args[1], op))^{$(decend(args[2], Val{:NoSurround}()))}" + # end + # end + # end, + # function equals(io::IO, expr, prevop, config) + # if head(expr) == :(=) + # return "$(decend(expr.args[1], expr.head)) = $(decend(expr.args[2], expr.head))" + # end + # end, + # function l_funcs(io::IO, ex, prevop, config) + # if head(ex) == :call && startswith(string(operation(ex)), "l_") + # l_func = string(operation(ex))[3:end] + # return "\\$(l_func){$(join(decend.(arguments(ex), prevop), "}{"))}" + # end + # end, +] + + +function build_if_else_body(io::IO, args, ifstr, elseifstr, elsestr) + _args = filter(x -> !(x isa LineNumberNode), args) + dargs = decend.(_args) + str = if length(_args) == 2 + """ + $(dargs[2]) & $ifstr $(dargs[1])""" + elseif length(_args) == 3 && head(_args[end]) == :elseif + """ + $(dargs[2]) & $ifstr $(dargs[1])\\\\ + $(dargs[3])""" + elseif length(_args) == 3 + """ + $(dargs[2]) & $ifstr $(dargs[1])\\\\ + $(dargs[3]) & $elsestr""" + else + throw(ArgumentError("Unexpected if/elseif/else statement to latexify. This could well be a Latexify.jl bug.")) + end + return str +end \ No newline at end of file From e870cd2b80370fabed84aed47841fbef4d8d68c1 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Mon, 12 Apr 2021 16:48:28 +0200 Subject: [PATCH 28/40] Reinstate recipes. --- src/latexify_function.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/latexify_function.jl b/src/latexify_function.jl index 2881865b..ca0a9877 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -4,6 +4,8 @@ function latexify(args...; env=:auto, kwargs...) empty!(CONFIG) merge!(CONFIG, DEFAULT_CONFIG, default_kwargs, kwargs) + ((_args,), _kwargs) = apply_recipe(args...; kwargs...) + merge!(CONFIG, _kwargs) if env==:auto call_result = iterate_top_matcher(args, CONFIG) @@ -11,6 +13,7 @@ function latexify(args...; env=:auto, kwargs...) func = OUTPUTFUNCTIONS[env] call_result = func(args...; CONFIG...) end +# call_result = latexraw((length(args) == 1 ? args[1] : args...); CONFIG...) COPY_TO_CLIPBOARD && clipboard(call_result) AUTO_DISPLAY && display(call_result) get(CONFIG, :render, false) && render(call_result) From 7c5f972d4104206db44e6cfc384e3703274bd37a Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Mon, 12 Apr 2021 16:51:51 +0200 Subject: [PATCH 29/40] WIP: play with recipes in recipes test. --- test/recipe_test.jl | 80 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/test/recipe_test.jl b/test/recipe_test.jl index f4771708..6f8bb13c 100644 --- a/test/recipe_test.jl +++ b/test/recipe_test.jl @@ -71,7 +71,7 @@ end end -using .MyModule +# using .MyModule t = MyModule.MyType([:A, :B, 3.], [1., 2, 3]) t2 = MyModule.MyType([:X, :Y, :(x/y)], Number[1.23434534, 232423.42345, 12//33]) @@ -202,4 +202,82 @@ raw"\begin{equation} \end{equation} ", "\r\n"=>"\n") +struct MyType + val +end +push!(Latexify.MATCHING_FUNCTIONS, +function _mytype(x, prevop, config) + if x isa MyType + decend(x.val) + end +end +) + +struct MyArray + val +end +push!(Latexify.MATCHING_FUNCTIONS, +function _myarray(x, prevop, config) + if x isa MyArray + decend(x.val) + end +end +) + +struct MyDoubleArray + arr1 + arr2 +end +push!(Latexify.MATCHING_FUNCTIONS, +function _mydoublearray(x, prevop, config) + if x isa MyDoubleArray + m = decend.(hcat(x.arr1, x.arr2)) + """ + \\begin{aligned} + $(join(join.(eachrow(m), " &= "), "\\\\\n")) + \\end{aligned}""" + end +end +) + +function _align(m::AbstractMatrix; decend=false, starred=false, rows=:all, eol = "\\\\\n", config...) + _m = rows == :all ? m : m[rows, :] + """ + \\begin{align$(starred ? "*" : "")} + $(join(join.(eachrow(_m), " &= "), eol)) + \\end{align$(starred ? "*" : "")}""" +end + +m = [1 2; 8 9; 11 12] +_align(m; rows=1:2, starred=true) |> println + + +t = MyType(:(x/y)) +a = MyArray([:(x/y), 1//2]) +d = MyDoubleArray([:(x/y), 1//2], [:hello, "x^2"]) + +latexify(t) +latexify(a) +latexify(d; env=:raw) +latexify(d) +render(latexify(d; env=:raw), MIME("application/pdf")) +render(latexify(d), MIME("application/pdf")) +latexify(d; env=:table) + +latexify(hcat(d.arr1, d.arr2); env=:align) + + + +Latexify.set_default(render=true) +latexify([1, 2//3, t]) + +using MacroTools +ex = @macroexpand @latexrecipe function f(x::MyType; hello=:hi) + env --> :equation + transpose --> true + return x.val +end +MacroTools.striplines(ex) +t = MyType(:(x/y)) +@time latexify(t) \ No newline at end of file From b208c68389630a041f5a9e9fa86e9d1c01e2ac03 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 16 Apr 2021 16:13:42 +0200 Subject: [PATCH 30/40] Way too much stuff for a commit. - Rename decend -> descend - Fix top-level matching for environments. - Add some precompilation. - Unify call signatures by changing config position. - Let config be a passed argument. This ensures that modifications are constrained to calls below the modifying call. - WIP: some macro prototyping. --- src/Latexify.jl | 15 +- src/config.jl | 9 +- src/latexarray.jl | 2 +- src/latexify_function.jl | 219 ++++++++++++++++++--------- src/latexraw.jl | 78 ++++++++-- src/latexraw_recipes.jl | 74 ++++----- src/matching_functions_test.jl | 269 ++++++++++++++++++++------------- 7 files changed, 432 insertions(+), 234 deletions(-) diff --git a/src/Latexify.jl b/src/Latexify.jl index dd3f07b2..a01d33fa 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -1,4 +1,5 @@ module Latexify +# Base.Experimental.@compiler_options optimize=0 compile=min infer=no using Requires using LaTeXStrings using InteractiveUtils @@ -52,7 +53,7 @@ include("numberformatters.jl") include("latexify_function.jl") # include("refactor_prototype_2.jl") -export decend, unpack, head, operation, arguments, nested +export descend, unpack, head, operation, arguments, nested macro generate_test(expr) @@ -123,6 +124,18 @@ end # latexify(_ex) # @assert precompile(latexify, (Expr,)) +latexify(:(z/y*s+1.2+1//2)) +for f in DEFAULT_INSTRUCTIONS + for op in [Expr, Symbol, Float64, Int, Rational, ComplexF64] + for config in [Dict, NamedTuple] + for prevop in [Symbol, Nothing] + precompile(f, (IOBuffer, config, op, prevop )) + end + end + end +end + + ### Add support for additional packages without adding them as dependencies. function __init__() @require DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" begin diff --git a/src/config.jl b/src/config.jl index faa149f8..3fc13459 100644 --- a/src/config.jl +++ b/src/config.jl @@ -1,4 +1,4 @@ -const DEFAULT_CONFIG = Dict{Symbol, Any}( +const DEFAULT_CONFIG = (; :mulsym => " \\cdot ", :convert_unicode => true, :strip_broadcast => true, @@ -19,12 +19,7 @@ const MODULE_CONFIG = Dict{Symbol, Any}() ## USE_CONFIG can store user-specified defaults const USER_CONFIG = Dict{Symbol, Any}() -## CONFIG is reset every latexify call. -const CONFIG = Dict{Symbol, Any}() -getconfig(key::Symbol) = CONFIG[key] - - -const comparison_operators = Dict( +const comparison_operators = (; :< => "<", :.< => "<", :> => ">", diff --git a/src/latexarray.jl b/src/latexarray.jl index f12dbc76..a6c1f1e2 100644 --- a/src/latexarray.jl +++ b/src/latexarray.jl @@ -16,7 +16,7 @@ function _latexarray(arr::AbstractArray) str = "\\left[\n" str *= "\\begin{array}{" * "$(adjustment)"^columns * "}\n" - arr = decend.(arr) + arr = descend.(arr) for i=1:rows, j=1:columns str *= arr[i,j] j==columns ? (str *= eol) : (str *= " & ") diff --git a/src/latexify_function.jl b/src/latexify_function.jl index ca0a9877..6597279a 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -1,29 +1,53 @@ -function latexify(args...; env=:auto, kwargs...) +function latexify(args...; kwargs...) + io = IOBuffer(; append=true) + config = (; DEFAULT_CONFIG..., MODULE_CONFIG..., USER_CONFIG..., default_kwargs..., kwargs...) + latexify(io, args...; kwargs...) + call_result = LaTeXString(String(take!(io))) + COPY_TO_CLIPBOARD && clipboard(call_result) + AUTO_DISPLAY && display(call_result) + get(config, :render, false) && render(call_result) + return call_result +end +function latexify(io::IO, args...; kwargs...) ## Let potential recipes transform the arguments. # kwargs = merge(default_kwargs, kwargs) - empty!(CONFIG) - merge!(CONFIG, DEFAULT_CONFIG, default_kwargs, kwargs) - ((_args,), _kwargs) = apply_recipe(args...; kwargs...) - merge!(CONFIG, _kwargs) + # empty!(CONFIG) + # merge!(CONFIG, DEFAULT_CONFIG, default_kwargs, kwargs) + # ((_args,), _kwargs) = apply_recipe(args...; kwargs...) + # merge!(CONFIG, _kwargs) + config = (; descend_counter = [0], DEFAULT_CONFIG..., MODULE_CONFIG..., USER_CONFIG..., default_kwargs..., kwargs...) + + - if env==:auto - call_result = iterate_top_matcher(args, CONFIG) - else - func = OUTPUTFUNCTIONS[env] - call_result = func(args...; CONFIG...) - end -# call_result = latexraw((length(args) == 1 ? args[1] : args...); CONFIG...) - COPY_TO_CLIPBOARD && clipboard(call_result) - AUTO_DISPLAY && display(call_result) - get(CONFIG, :render, false) && render(call_result) - return call_result + # if env==:auto + # call_result = iterate_top_matcher(args, CONFIG) + # else + # func = OUTPUTFUNCTIONS[env] + # call_result = func(args...; CONFIG...) + # end + # _args = length(args) == 1 ? args[1] : args + empty!(INSTRUCTIONS) + append!(INSTRUCTIONS, DEFAULT_INSTRUCTIONS, USER_INSTRUCTIONS, ENV_INSTRUCTIONS) + + # iterate_top_matcher(io, config, args, :_nothing) + + # for f in env_instructions[end:-1:1] + # call_matched = f(io, config, e, prevop)::Bool + # if call_matched ## then we're done and io is populated! + # return nothing + # end + # end + # call_matched || throw(ArgumentError("No suitable environment found for $e")) + descend(io, config, args) + # latexraw(io, config, args) end -function iterate_top_matcher(args, kwargs) - for f in TOP_LEVEL_MATCHERS[end:-1:1] - call_result = f(args, kwargs) - if !(call_result === nothing) +function iterate_top_matcher(io, config, args, prevop=:_nothing) + env_instructions = vcat(ENV_INSTRUCTIONS, USER_ENV_INSTRUCTIONS) + for f in env_instructions[end:-1:1] + call_result = f(io, config, args, prevop) + if call_result return call_result end end @@ -32,53 +56,112 @@ end apply_recipe(args...; kwargs...) = (args, kwargs) -const OUTPUTFUNCTIONS = Dict( - :inline => _latexinline, - :tabular => _latextabular, - :table => _latextabular, - :raw => latexraw, - :array => _latexarray, - :align => _latexalign, - :aligned => (args...; kwargs...) -> _latexbracket(_latexalign(args...; kwargs..., aligned=true, starred=false); kwargs...), - :eq => _latexequation, - :equation => _latexequation, - :bracket => _latexbracket, - :mdtable => mdtable, - :mdtext => mdtext, - ) -const TOP_LEVEL_MATCHERS = [ - function _inline_fallback(args, kwargs) - return latexstring(latexraw(args...; kwargs...)) - end, - function _equation_array(args, kwargs) - if eltype(args) <: AbstractArray || eltype(args) <: Tuple - return _latexequation(args...; kwargs...) - end - end, - function _align(args, kwargs) - if length(args) == 2 && (eltype(args) <: AbstractVector) - return _latexalign(args...; kwargs...) - end - end, - function _dicts(args, kwargs) - if length(args) == 1 && (args[1] isa AbstractDict || args[1] isa NamedTuple) - _latexalign(collect(keys(args[1])), collect(values(args[1])); kwargs...) - end - end, - function _equation(args, kwargs) - if length(args) == 1 && args[1] isa AbstractArray && eltype(args[1]) <: AbstractArray - try - x = reduce(hcat, args[1]) - return _latexequation(args...; kwargs...) - catch - return _latexinline(args...; kwargs...) - end - end - end, - function _latexstring_passthrough(args, kwargs) - if length(args) == 1 && args[1] isa LaTeXString - return args[1] +abstract type AbstractLatexifyEnvironment end +abstract type AbstractLatexEnvironment <: AbstractLatexifyEnvironment end +for T in [:Inline, :Equation, :Align, :Bracket, :Table, :NoEnv, :Aligned, :Array] + @eval(struct $T <: AbstractLatexEnvironment + args + end) +end +struct AutoEnv <: AbstractLatexEnvironment + args + env::Symbol +end +AutoEnv(args; default=:auto) = AutoEnv(args, default) + +abstract type AbstractMarkdownEnvironment <: AbstractLatexifyEnvironment end +for T in [:MDTable, :MDText] + @eval(struct $T <: AbstractMarkdownEnvironment + args + end) +end + + +wrap_env(x) = AutoEnv(x) +wrap_end(x::AbstractLatexifyEnvironment) = x + + +const OUTPUTFUNCTIONS = (; + :inline => Inline, + :tabular => Table, + :table => Table, + :raw => NoEnv, + :none => NoEnv, + :noenv => NoEnv, + :align => Align, + :eq => Equation, + :equation => Equation, + :bracket => Bracket, + :mdtable => MDTable, + :mdtext => MDText, + ) + +const ENV_INSTRUCTIONS = [ + function _auto_environment(io::IO, config, x, prevop) + if x isa Tuple{AutoEnv} || (x isa Tuple && config[:descend_counter] == [1]) + args = x isa Tuple{AutoEnv} ? x[1].args : x + _default_env = x isa Tuple{AutoEnv} ? x[1].env : :_nothing + env = get(config, :env, _default_env) + if haskey(OUTPUTFUNCTIONS, env) + descend(io, config, OUTPUTFUNCTIONS[env](x.args), prevop) + elseif args isa Tuple{Vector, Vector} + descend(io, config, (Align(hcat(args[1], args[2])),)) + elseif length(args) == 1 + descend(io, config, (Inline(args[1]),)) + else + throw(MethodError("Unspported argument to `latexify`: $args")) end - end, -] \ No newline at end of file + return true + end + return false + end, + function _no_env(io::IO, config, x, prevop) + if x isa Tuple{NoEnv} + descend(io, config, x[1].args, prevop) + return true + end + return false + end, + function _inline(io::IO, config, x, prevop) + if x isa Tuple{Inline} + write(io, "\$") + descend(io, config, x[1].args, prevop) + write(io, "\$") + return true + end + return false + end, + function _align(io::IO, config, x, prevop) + if x isa Tuple{Align} + write(io, "\\begin{align}\n") + join_matrix(io, config, x[1].args, " &= ") + write(io, "\n\\end{align}") + return true + end + return false + end, + function _equation(io::IO, config, x, prevop) + if x isa Tuple{Equation} + write(io, "\\begin{equation}\n") + descend(io, config, x[1].args, prevop) + write(io, "\n\\end{equation}") + return true + end + return false + end, +] + +function join_matrix(io, config, m, delim = " & ", eol="\\\\\n") + nrows, ncols = size(m) + mime = MIME("text/latexify") + for (i, row) in enumerate(eachrow(m)) + for x in row[1:end-1] + descend(io, config, x) + write(io, delim) + end + descend(io, config, row[end]) + i != nrows ? write(io, eol) : write(io, "\n") + end + return nothing +end \ No newline at end of file diff --git a/src/latexraw.jl b/src/latexraw.jl index 71ed6b15..82c5b133 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -1,14 +1,59 @@ -function latexraw(expr; kwargs...) - empty!(CONFIG) - merge!(CONFIG, DEFAULT_CONFIG, kwargs) - io = IOBuffer(; append=true) - decend(io, expr) - str = String(take!(io)) -# CONFIG[:convert_unicode] && (str = unicode2latex(str)) - return LaTeXString(str) +const INSTRUCTIONS = Function[] +const USER_INSTRUCTIONS = Function[] +const USER_ENV_INSTRUCTIONS = Function[] + +# function latexraw(io::IO, config::NamedTuple, expr) +# # empty!(CONFIG) +# # merge!(CONFIG, DEFAULT_CONFIG, kwargs) +# empty!(INSTRUCTIONS) +# append!(INSTRUCTIONS, DEFAULT_INSTRUCTIONS, USER_INSTRUCTIONS, ENV_INSTRUCTIONS) +# descend(io, config, expr) +# # CONFIG[:convert_unicode] && (str = unicode2latex(str)) +# return nothing +# end + + +function capture_function_signature(ex) + ex.head ∉ [:function, :(=)] && throw(ArgumentError("Malformed function signature to latexify recipe macro.")) + annotated_args = ex.args[1].args[2:end] + types = Tuple(getindex.(getfield.(annotated_args, :args), 2)) + vars = Tuple(getindex.(getfield.(annotated_args, :args), 1)) + return vars, types +end + +function capture_function_body(ex) + if ex.head == :function + elseif ex.head == :(=) + return ex.args[2] + else + throw(ArgumentError("Malformed function signature to latexify recipe macro.")) + end end -add_matcher(f) = push!(MATCHING_FUNCTIONS, f) +macro latextype(expr) + vars, types = capture_function_signature(expr) + body = esc(capture_function_body(expr)) + io, x, prevop, config = gensym(:io), gensym(:x), gensym(:prevop), gensym(:config) + body = MacroTools.postwalk(x -> x isa Expr && x.head == :call && x.args[1] == :descend ? Expr(x.head, x.args[1], io, x.args[2], prevop, config) : x, body) + + fname = gensym(:recipe_fn) + return esc(quote + push!(USER_INSTRUCTIONS, + function $(fname)($io, $x, $prevop, $config) + $x isa Tuple && length($x) == $(length(vars)) || return false + types = $types + for i in $(eachindex(vars)) + typeof($x[i]) <: types[i] || return false + end + $(vars) = $x + $x[1] isa $(types[1]) || return false + $body + return true + end + ) + end) +end +# add_matcher(f) = push!(MATCHING_FUNCTIONS, f) # _check_call_match(e, op::Symbol) = e isa Expr && e.head === :call && e.args[1] === op # _check_call_match(e, op::AbstractArray) = e isa Expr && e.head === :call && e.args[1] ∈ op @@ -33,10 +78,11 @@ head(ex::Expr) = ex.head unpack(x) = (head(x), operation(x), arguments(x)) -function decend(io::IO, e, prevop=Val(:_nothing))::Nothing - for f in MATCHING_FUNCTIONS_TEST[end:-1:1] - call_result = f(io, e, prevop, CONFIG) - if !(call_result === nothing) +function descend(io::IO, config::NamedTuple, e, prevop=:(_nothing))::Nothing + config[:descend_counter][1] += 1 + for f in INSTRUCTIONS[end:-1:1] + call_matched = f(io, config, e, prevop)::Bool + if call_matched return nothing break end @@ -46,10 +92,10 @@ end surround(x) = "\\left( $x \\right)" -function join_decend(io::IO, args, delim; prevop=nothing) +function join_descend(io::IO, config, args, delim; prevop=nothing) for arg in args[1:end-1] - decend(io, arg, prevop) + descend(io, config, arg, prevop) write(io, delim) end - decend(io, args[end], prevop) + descend(io, config, args[end], prevop) end \ No newline at end of file diff --git a/src/latexraw_recipes.jl b/src/latexraw_recipes.jl index c2b372ad..c5c3a34d 100644 --- a/src/latexraw_recipes.jl +++ b/src/latexraw_recipes.jl @@ -16,12 +16,12 @@ const MATCHING_FUNCTIONS = [ if op isa Symbol funcname = string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) else - funcname = decend(op) + funcname = descend(op) end if length(args) >= 1 && head(args[1]) == :parameters - _arg = "\\left( $(join(decend.(args[2:end]), ", ")); $(decend(args[1])) \\right)" + _arg = "\\left( $(join(descend.(args[2:end]), ", ")); $(descend(args[1])) \\right)" else - _arg = "\\left( " * join(decend.(args), ", ") * " \\right)" + _arg = "\\left( " * join(descend.(args), ", ") * " \\right)" end return funcname * _arg end @@ -65,15 +65,15 @@ const MATCHING_FUNCTIONS = [ end, function _oneline_function(ex, prevop, config) if head(ex) == :function && length(arguments(ex)) == 1 - return "$(decend(operation(ex), head(ex))) = $(decend(arguments(ex)[1], head(ex)))" + return "$(descend(operation(ex), head(ex))) = $(descend(arguments(ex)[1], head(ex)))" end end, function _return(ex, prevop, config) - head(ex) == :return && length(arguments(ex)) == 0 ? decend(operation(ex)) : nothing + head(ex) == :return && length(arguments(ex)) == 0 ? descend(operation(ex)) : nothing end, function _chained_comparisons(ex, _...) if head(ex) == :comparison && Symbol.(arguments(ex)[1:2:end]) ⊆ keys(comparison_operators) - str = join([isodd(i) ? "$var" : comparison_operators[Symbol(var)] for (i, var) in enumerate(decend.(vcat(operation(ex), arguments(ex))))], " ") + str = join([isodd(i) ? "$var" : comparison_operators[Symbol(var)] for (i, var) in enumerate(descend.(vcat(operation(ex), arguments(ex))))], " ") str = "\\left( $str \\right)" return str end @@ -88,27 +88,27 @@ const MATCHING_FUNCTIONS = [ end end, function _wedge(ex, prevop, config) - head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\wedge $(decend(arguments(ex)[1]))" : nothing + head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\wedge $(descend(arguments(ex)[1]))" : nothing end, function _vee(ex, prevop, config) - head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\vee $(decend(arguments(ex)[1]))" : nothing + head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\vee $(descend(arguments(ex)[1]))" : nothing end, function _negation(ex, prevop, config) operation(ex) == :(!) ? "\\neg $(arguments(ex)[1])" : nothing end, function _kw(x, args...) - head(x) == :kw ? "$(decend(operation(x))) = $(decend(arguments(x)[1]))" : nothing + head(x) == :kw ? "$(descend(operation(x))) = $(descend(arguments(x)[1]))" : nothing end, function _parameters(x, args...) - head(x) == :parameters ? join(decend.(vcat(operation(x), arguments(x))), ", ") : nothing + head(x) == :parameters ? join(descend.(vcat(operation(x), arguments(x))), ", ") : nothing end, function _indexing(x, prevop, config) if head(x) == :ref if getconfig(:index) == :subscript return "$(operation(x))_{$(join(arguments(x), ","))}" elseif getconfig(:index) == :bracket - argstring = join(decend.(arguments(x)), ", ") - return "$(decend(operation(x)))\\left[$argstring\\right]" + argstring = join(descend.(arguments(x)), ", ") + return "$(descend(operation(x)))\\left[$argstring\\right]" else throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket")) end @@ -116,12 +116,12 @@ const MATCHING_FUNCTIONS = [ end, function _broadcast_macro(ex, prevop, config) if head(ex) == :macrocall && operation(ex) == Symbol("@__dot__") - return decend(arguments(ex)[end]) + return descend(arguments(ex)[end]) end end, function _block(x, args...) if head(x) == :block - return decend(vcat(operation(x), arguments(x))[end]) + return descend(vcat(operation(x), arguments(x))[end]) end end, function number(x, prevop, config) @@ -137,22 +137,22 @@ const MATCHING_FUNCTIONS = [ function rational_expr(x, prevop, config) if operation(x) == :// if arguments(x)[2] == 1 - return decend(arguments[1], prevop) + return descend(arguments[1], prevop) else - decend(:($(arguments(x)[1])/$(arguments(x)[2])), prevop) + descend(:($(arguments(x)[1])/$(arguments(x)[2])), prevop) end end end, function rational(x, prevop, config) if x isa Rational - str = x.den == 1 ? decend(x.num, prevop) : decend(:($(x.num)/$(x.den)), prevop) + str = x.den == 1 ? descend(x.num, prevop) : descend(:($(x.num)/$(x.den)), prevop) prevop ∈ [:*, :^] && (str = surround(str)) return str end end, function complex(z, prevop, config) if z isa Complex - str = "$(decend(z.re))$(z.im < 0 ? "-" : "+" )$(decend(abs(z.im)))\\textit{i}" + str = "$(descend(z.re))$(z.im < 0 ? "-" : "+" )$(descend(abs(z.im)))\\textit{i}" prevop ∈ [:*, :^] && (str = surround(str)) return str end @@ -193,14 +193,14 @@ const MATCHING_FUNCTIONS = [ h, op, args = unpack(expr) # if (expr isa LatexifyOperation || h == :LatexifyOperation) && op == :merge if h == :call && op == :latexifymerge - join(decend.(args), "") + join(descend.(args), "") end end, function parse_string(str, prevop, config) if str isa AbstractString try ex = Meta.parse(str) - return decend(ex, prevop) + return descend(ex, prevop) catch ParseError error(""" in Latexify.jl: @@ -233,37 +233,37 @@ const MATCHING_FUNCTIONS = [ function strip_broadcast_dot(expr, prevop, config) h, op, args = unpack(expr) if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') - return string(decend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) + return string(descend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) end end, function strip_broadcast_dot_call(expr, prevop, config) h, op, args = unpack(expr) if expr isa Expr && config[:strip_broadcast] && h == :. - return decend(Expr(:call, op, args[1].args...), prevop) + return descend(Expr(:call, op, args[1].args...), prevop) end end, function plusminus(expr, prevop, config) h, op, args = unpack(expr) if h == :call && op == :± - return "$(decend(args[1], op)) \\pm $(decend(args[2], op))" + return "$(descend(args[1], op)) \\pm $(descend(args[2], op))" end end, function division(expr, prevop, config) h, op, args = unpack(expr) if h == :call && op == :/ - "\\frac{$(decend(args[1], op))}{$(decend(args[2], op))}" + "\\frac{$(descend(args[1], op))}{$(descend(args[2], op))}" end end, function multiplication(expr, prevop, config) h, op, args = unpack(expr) if h == :call && op == :* - join(decend.(args, op), "$(config[:mulsym])") + join(descend.(args, op), "$(config[:mulsym])") end end, function addition(expr, prevop, config) h, op, args = unpack(expr) if h == :call && op == :+ - str = join(decend.(args, op), " + ") + str = join(descend.(args, op), " + ") str = replace(str, "+ -"=>"-") prevop ∈ [:*, :^] && (str = surround(str)) return str @@ -276,10 +276,10 @@ const MATCHING_FUNCTIONS = [ if h == :call && op == :- if length(args) == 1 if operation(args[1]) == :- && length(arguments(args[1])) == 1 - return decend(arguments(args[1])[1], prevop) + return descend(arguments(args[1])[1], prevop) elseif args[1] isa Number && sign(args[1]) == -1 # return _latexraw(-args[1]; config...) - return decend(-args[1], op) + return descend(-args[1], op) else _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] return prevop == :^ ? surround("$op$_arg") : "$op$_arg" @@ -287,15 +287,15 @@ const MATCHING_FUNCTIONS = [ elseif length(args) == 2 if args[2] isa Number && sign(args[2]) == -1 - return "$(decend(args[1], :+)) + $(decend(-args[2], :+))" + return "$(descend(args[1], :+)) + $(descend(-args[2], :+))" end if operation(args[2]) == :- && length(arguments(args[2])) == 1 - return "$(decend(args[1], :+)) + $(decend(arguments(args[2])[1], :+))" + return "$(descend(args[1], :+)) + $(descend(arguments(args[2])[1], :+))" end if operation(args[2]) ∈ [:-, :.-, :+, :.+] - return "$(decend(args[1], op)) - $(surround(decend(args[2], op)))" + return "$(descend(args[1], op)) - $(surround(descend(args[2], op)))" end - str = join(decend.(args, op), " - ") + str = join(descend.(args, op), " - ") prevop ∈ [:*, :^] && (str = surround(str)) return str end @@ -307,21 +307,21 @@ const MATCHING_FUNCTIONS = [ if operation(args[1]) in Latexify.trigonometric_functions fsym = operation(args[1]) fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") - "$fstring^{$(decend(args[2], op))}\\left( $(join(decend.(arguments(args[1]), operation(args[1])), ", ")) \\right)" + "$fstring^{$(descend(args[2], op))}\\left( $(join(descend.(arguments(args[1]), operation(args[1])), ", ")) \\right)" else - "$(decend(args[1], op))^{$(decend(args[2], Val{:NoSurround}()))}" + "$(descend(args[1], op))^{$(descend(args[2], Val{:NoSurround}()))}" end end end, function equals(expr, prevop, config) if head(expr) == :(=) - return "$(decend(expr.args[1], expr.head)) = $(decend(expr.args[2], expr.head))" + return "$(descend(expr.args[1], expr.head)) = $(descend(expr.args[2], expr.head))" end end, function l_funcs(ex, prevop, config) if head(ex) == :call && startswith(string(operation(ex)), "l_") l_func = string(operation(ex))[3:end] - return "\\$(l_func){$(join(decend.(arguments(ex), prevop), "}{"))}" + return "\\$(l_func){$(join(descend.(arguments(ex), prevop), "}{"))}" end end, ] @@ -329,7 +329,7 @@ const MATCHING_FUNCTIONS = [ function build_if_else_body(args, ifstr, elseifstr, elsestr) _args = filter(x -> !(x isa LineNumberNode), args) - dargs = decend.(_args) + dargs = descend.(_args) str = if length(_args) == 2 """ $(dargs[2]) & $ifstr $(dargs[1])""" diff --git a/src/matching_functions_test.jl b/src/matching_functions_test.jl index 7d4d4f58..81dedb13 100644 --- a/src/matching_functions_test.jl +++ b/src/matching_functions_test.jl @@ -1,15 +1,25 @@ -const MATCHING_FUNCTIONS_TEST = [ - function report_bad_call(io::IO, expr, prevop, config) +const DEFAULT_INSTRUCTIONS = [ + function report_bad_call(io::IO, config, expr, prevop) println(""" Unsupported input with expr=$expr prevop=$prevop and config=$config """) - return nothing + return false + return false end, - function call(io::IO, expr, prevop, config) + # function _unpack_args(io::IO, config, args, prevop) + # ### If no function has claimed the tuple, unpack it and try again. + # ### This is mainly to unpack the args to `latexify(args...)`. + # if args isa Tuple && length(args) == 1 + # descend(io, config, args[1], prevop) + # return true + # end + # return false + # end, + function call(io::IO, config, expr, prevop) if expr isa Expr && head(expr) == :call h, op, args = unpack(expr) @@ -17,77 +27,91 @@ const MATCHING_FUNCTIONS_TEST = [ funcname = string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) write(io, funcname) else - decend(io, op) + descend(io, config, op) end if length(args) >= 1 && head(args[1]) == :parameters write(io, "\\left( ") - join_decend(io, args[2:end], ", ") + join_descend(io, config, args[2:end], ", ") write(io, "; ") - decend(io, args[1]) + descend(io, config, args[1]) write(io, " \\right)") - # _arg = "\\left( $(join(decend.(args[2:end]), ", ")); $(decend(args[1])) \\right)" + # _arg = "\\left( $(join(descend.(args[2:end]), ", ")); $(descend(args[1])) \\right)" else write(io, "\\left( ") - join_decend(io, args, ", ") + join_descend(io, config, args, ", ") write(io, " \\right)") - # _arg = "\\left( " * join(decend.(args), ", ") * " \\right)" + # _arg = "\\left( " * join(descend.(args), ", ") * " \\right)" end return true end + return false end, - # function _sqrt(io::IO, ex, prevop, config) + function _aligned(io::IO, config, expr, prevop) + expr isa Tuple{T, T} where T <: AbstractArray || return false + display("here") + return false + end, + # function _sqrt(io::IO, config, ex, prevop) # operation(ex) == :sqrt ? "\\$(operation(ex)){$(arguments(ex)[1])}" : nothing + # return false # end, - # function _abs(io::IO, ex, prevop, config) + # function _abs(io::IO, config, ex, prevop) # operation(ex) == :abs ? "\\left\\|$(arguments(ex)[1])\\right\\|" : nothing + # return false # end, - # function _single_comparison(io::IO, ex, args...) + # function _single_comparison(io::IO, config, ex, args...) # if operation(ex) ∈ keys(comparison_operators) && length(arguments(ex)) == 2 # str = "$(arguments(ex)[1]) $(comparison_operators[operation(ex)]) $(arguments(ex)[2])" # str = "\\left( $str \\right)" # return str # end + # return false # end, - # function _if(io::IO, ex, prevop, config) + # function _if(io::IO, config, ex, prevop) # if ex isa Expr && head(ex) == :if # str = build_if_else_body( # ex.args, - # getconfig(:ifstr), - # getconfig(:elseifstr), - # getconfig(:elsestr) + # config[:ifstr], + # config[:elseifstr], + # config[:elsestr] # ) # return """ # \\begin{cases} # $str # \\end{cases}""" # end + # return false # end, - # function _elseif(io::IO, ex, prevop, config) + # function _elseif(io::IO, config, ex, prevop) # if ex isa Expr && head(ex) == :elseif # str = build_if_else_body( # ex.args, - # getconfig(:elseifstr), - # getconfig(:elseifstr), - # getconfig(:elsestr) + # config[:elseifstr], + # config[:elseifstr], + # config[:elsestr] # ) # end + # return false # end, - # function _oneline_function(io::IO, ex, prevop, config) + # function _oneline_function(io::IO, config, ex, prevop) # if head(ex) == :function && length(arguments(ex)) == 1 - # return "$(decend(operation(ex), head(ex))) = $(decend(arguments(ex)[1], head(ex)))" + # return "$(descend(operation(ex), head(ex))) = $(descend(arguments(ex)[1], head(ex)))" # end + # return false # end, - # function _return(io::IO, ex, prevop, config) - # head(ex) == :return && length(arguments(ex)) == 0 ? decend(operation(ex)) : nothing + # function _return(io::IO, config, ex, prevop) + # head(ex) == :return && length(arguments(ex)) == 0 ? descend(operation(ex)) : nothing + # return false # end, - # function _chained_comparisons(io::IO, ex, _...) + # function _chained_comparisons(io::IO, config, ex, _...) # if head(ex) == :comparison && Symbol.(arguments(ex)[1:2:end]) ⊆ keys(comparison_operators) - # str = join([isodd(i) ? "$var" : comparison_operators[Symbol(var)] for (i, var) in enumerate(decend.(vcat(operation(ex), arguments(ex))))], " ") + # str = join([isodd(i) ? "$var" : comparison_operators[Symbol(var)] for (i, var) in enumerate(descend.(vcat(operation(ex), arguments(ex))))], " ") # str = "\\left( $str \\right)" # return str # end + # return false # end, - # function _type_annotation(io::IO, ex, prevop, config) + # function _type_annotation(io::IO, config, ex, prevop) # if head(ex) == :(::) # if length(arguments(ex)) == 0 # return "::$(operation(ex))" @@ -95,123 +119,145 @@ const MATCHING_FUNCTIONS_TEST = [ # return "$(operation(ex))::$(arguments(ex)[1])" # end # end + # return false # end, - # function _wedge(io::IO, ex, prevop, config) - # head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\wedge $(decend(arguments(ex)[1]))" : nothing + # function _wedge(io::IO, config, ex, prevop) + # head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\wedge $(descend(arguments(ex)[1]))" : nothing + # return false # end, - # function _vee(io::IO, ex, prevop, config) - # head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(decend(operation(ex))) \\vee $(decend(arguments(ex)[1]))" : nothing + # function _vee(io::IO, config, ex, prevop) + # head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\vee $(descend(arguments(ex)[1]))" : nothing + # return false # end, - # function _negation(io::IO, ex, prevop, config) + # function _negation(io::IO, config, ex, prevop) # operation(ex) == :(!) ? "\\neg $(arguments(ex)[1])" : nothing + # return false # end, - # function _kw(io::IO, x, args...) - # head(x) == :kw ? "$(decend(operation(x))) = $(decend(arguments(x)[1]))" : nothing + # function _kw(io::IO, config, x, args...) + # head(x) == :kw ? "$(descend(operation(x))) = $(descend(arguments(x)[1]))" : nothing + # return false # end, - # function _parameters(io::IO, x, args...) - # head(x) == :parameters ? join(decend.(vcat(operation(x), arguments(x))), ", ") : nothing + # function _parameters(io::IO, config, x, args...) + # head(x) == :parameters ? join(descend.(vcat(operation(x), arguments(x))), ", ") : nothing + # return false # end, - # function _indexing(io::IO, x, prevop, config) + # function _indexing(io::IO, config, x, prevop) # if head(x) == :ref - # if getconfig(:index) == :subscript + # if config[:index] == :subscript # return "$(operation(x))_{$(join(arguments(x), ","))}" - # elseif getconfig(:index) == :bracket - # argstring = join(decend.(arguments(x)), ", ") - # return "$(decend(operation(x)))\\left[$argstring\\right]" + # elseif config[:index] == :bracket + # argstring = join(descend.(arguments(x)), ", ") + # return "$(descend(operation(x)))\\left[$argstring\\right]" # else # throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket")) # end # end + # return false # end, - # function _broadcast_macro(io::IO, ex, prevop, config) + # function _broadcast_macro(io::IO, config, ex, prevop) # if head(ex) == :macrocall && operation(ex) == Symbol("@__dot__") - # return decend(arguments(ex)[end]) + # return descend(arguments(ex)[end]) # end + # return false # end, - # function _block(io::IO, x, args...) + # function _block(io::IO, config, x, args...) # if head(x) == :block - # return decend(vcat(operation(x), arguments(x))[end]) + # return descend(vcat(operation(x), arguments(x))[end]) # end + # return false # end, - function number(io::IO, x, prevop, config) + function number(io::IO, config, x, prevop) if x isa Number try isinf(x) && write(io, "\\infty") && return true; catch; end - fmt = getconfig(:fmt) + fmt = config[:fmt] fmt isa String && (fmt = PrintfNumberFormatter(fmt)) str = string(fmt(x)) sign(x) == -1 && prevop == :^ && (str = surround(str)) write(io, str) return true end + return false end, - # function rational_expr(io::IO, x, prevop, config) + # function rational_expr(io::IO, config, x, prevop) # if operation(x) == :// # if arguments(x)[2] == 1 - # return decend(arguments[1], prevop) + # return descend(arguments[1], prevop) # else - # decend:($(arguments(x)[1])/$(arguments(x)[2])), prevop) + # descend:($(arguments(x)[1])/$(arguments(x)[2])), prevop) # end # end + # return false # end, - # function rational(io::IO, x, prevop, config) + # function rational(io::IO, config, x, prevop) # if x isa Rational - # str = x.den == 1 ? decend(x.num, prevop) : decend(:($(x.num)/$(x.den)), prevop) + # str = x.den == 1 ? descend(x.num, prevop) : descend(:($(x.num)/$(x.den)), prevop) # prevop ∈ [:*, :^] && (str = surround(str)) # return str # end + # return false # end, - # function complex(io::IO, z, prevop, config) + # function complex(io::IO, config, z, prevop) # if z isa Complex - # str = "$(decend(z.re))$(z.im < 0 ? "-" : "+" )$(decend(abs(z.im)))\\textit{i}" + # str = "$(descend(z.re))$(z.im < 0 ? "-" : "+" )$(descend(abs(z.im)))\\textit{i}" # prevop ∈ [:*, :^] && (str = surround(str)) # return str # end + # return false # end, - # function _missing(io::IO, x, prevop, config) + # function _missing(io::IO, config, x, prevop) # if ismissing(x) # "\\textrm{NA}" # end + # return false # end, - # function _nothing(io::IO, x, prevop, config) + # function _nothing(io::IO, config, x, prevop) # x === nothing ? "" : nothing + # return false # end, - function symbol(io::IO, sym, _, config) + function symbol(io::IO, config, sym, _) if sym isa Symbol str = string(sym == :Inf ? :∞ : sym) str = convert_subscript(str) - getconfig(:convert_unicode) && (str = unicode2latex(str)) + config[:convert_unicode] && (str = unicode2latex(str)) write(io, str) return true end + return false end, - # function _char(io::IO, c, args...) + # function _char(io::IO, config, c, args...) # c isa Char ? string(c) : nothing + # return false # end, - # function array(io::IO, x, args...) + # function array(io::IO, config, x, args...) # x isa AbstractArray ? _latexarray(x) : nothing + # return false # end, - # function tuple(io::IO, x, args...) + # function tuple(io::IO, config, x, args...) # x isa Tuple ? _latexarray(x) : nothing + # return false # end, - # function vect_exp(io::IO, x, args...) + # function vect_exp(io::IO, config, x, args...) # head(x) ∈ [:vect, :vcat] ? _latexarray(expr_to_array(x)) : nothing + # return false # end, - # function hcat_exp(io::IO, x, args...) + # function hcat_exp(io::IO, config, x, args...) # # head(x)==:hcat ? _latexarray(permutedims(vcat(operation(x), arguments(x)))) : nothing # head(x)==:hcat ? _latexarray(expr_to_array(x)) : nothing + # return false # end, # (expr, prevop, config) -> begin # h, op, args = unpack(expr) # # if (expr isa LatexifyOperation || h == :LatexifyOperation) && op == :merge # if h == :call && op == :latexifymerge - # join(decend.(args), "") + # join(descend.(args), "") # end + # return false # end, - # function parse_string(io::IO, str, prevop, config) + # function parse_string(io::IO, config, str, prevop) # if str isa AbstractString # try # ex = Meta.parse(str) - # return decend(ex, prevop) + # return descend(ex, prevop) # catch ParseError # error(""" # in Latexify.jl: @@ -231,75 +277,87 @@ const MATCHING_FUNCTIONS_TEST = [ # """) # end # end + # return false # end, - # function _pass_through_LaTeXString_substrings(io::IO, str, args...) + # function _pass_through_LaTeXString_substrings(io::IO, config, str, args...) # str isa SubString{LaTeXString} ? String(str) : nothing + # return false # end, - # function _pass_through_LaTeXString(io::IO, str, args...) + # function _pass_through_LaTeXString(io::IO, config, str, args...) # str isa LaTeXString ? str.s : nothing + # return false # end, - # function _tuple_expr(io::IO, expr, prevop, config) + # function _tuple_expr(io::IO, config, expr, prevop) # head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing + # return false # end, - # function strip_broadcast_dot(io::IO, expr, prevop, config) + # function strip_broadcast_dot(io::IO, config, expr, prevop) # h, op, args = unpack(expr) # if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') - # return string(decend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) + # return string(descend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) # end + # return false # end, - # function strip_broadcast_dot_call(io::IO, expr, prevop, config) + # function strip_broadcast_dot_call(io::IO, config, expr, prevop) # h, op, args = unpack(expr) # if expr isa Expr && config[:strip_broadcast] && h == :. - # return decend(Expr(:call, op, args[1].args...), prevop) + # return descend(Expr(:call, op, args[1].args...), prevop) # end + # return false # end, - # function plusminus(io::IO, expr, prevop, config) + # function plusminus(io::IO, config, expr, prevop) # h, op, args = unpack(expr) # if h == :call && op == :± - # return "$(decend(args[1], op)) \\pm $(decend(args[2], op))" + # return "$(descend(args[1], op)) \\pm $(descend(args[2], op))" # end + # return false # end, - function division(io::IO, expr, prevop, config) + function division(io::IO, config, expr, prevop) h, op, args = unpack(expr) if h == :call && op == :/ write(io, "\\frac{") - decend(io, args[1], op) + descend(io, config, args[1], op) write(io, "}{") - decend(io, args[2], op) + descend(io, config, args[2], op) write(io, "}") - # "\\frac{$(decend(args[1], op))}{$(decend(args[2], op))}" + # "\\frac{$(descend(args[1], op))}{$(descend(args[2], op))}" return true end + return false end, - # function multiplication(io::IO, expr, prevop, config) - # h, op, args = unpack(expr) - # if h == :call && op == :* - # join(decend.(args, op), "$(config[:mulsym])") - # end - # end, - function addition(io::IO, expr, prevop, config) + function multiplication(io::IO, config, expr, prevop) + h, op, args = unpack(expr) + if h == :call && op == :* + # join(descend.(args, op), "$(config[:mulsym])") + join_descend(io, config, args, config[:mulsym]) + return true + end + return false + end, + function addition(io::IO, config, expr, prevop) h, op, args = unpack(expr) if h == :call && op == :+ prevop ∈ [:*, :^] && write(io, "\\left( ") - str = join_decend(io, args, " + ") + str = join_descend(io, config, args, " + ") # str = replace(str, "+ -"=>"-") # prevop ∈ [:*, :^] && (str = surround(str)) prevop ∈ [:*, :^] && write(io, " \\right)") # write() return true end + return false end, - # function subtraction(io::IO, expr, prevop, config) + # function subtraction(io::IO, config, expr, prevop) # # this one is so gnarly because it tries to fix stuff like - - or -(-(x-y)) # # -(x) is also a bit different to -(x, y) which does not make things simpler # h, op, args = unpack(expr) # if h == :call && op == :- # if length(args) == 1 # if operation(args[1]) == :- && length(arguments(args[1])) == 1 - # return decend(arguments(args[1])[1], prevop) + # return descend(arguments(args[1])[1], prevop) # elseif args[1] isa Number && sign(args[1]) == -1 # # return _latexraw(-args[1]; config...) - # return decend(-args[1], op) + # return descend(-args[1], op) # else # _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] # return prevop == :^ ? surround("$op$_arg") : "$op$_arg" @@ -307,49 +365,52 @@ const MATCHING_FUNCTIONS_TEST = [ # elseif length(args) == 2 # if args[2] isa Number && sign(args[2]) == -1 - # return "$(decend(args[1], :+)) + $(decend(-args[2], :+))" + # return "$(descend(args[1], :+)) + $(descend(-args[2], :+))" # end # if operation(args[2]) == :- && length(arguments(args[2])) == 1 - # return "$(decend(args[1], :+)) + $(decend(arguments(args[2])[1], :+))" + # return "$(descend(args[1], :+)) + $(descend(arguments(args[2])[1], :+))" # end # if operation(args[2]) ∈ [:-, :.-, :+, :.+] - # return "$(decend(args[1], op)) - $(surround(decend(args[2], op)))" + # return "$(descend(args[1], op)) - $(surround(descend(args[2], op)))" # end - # str = join(decend.(args, op), " - ") + # str = join(descend.(args, op), " - ") # prevop ∈ [:*, :^] && (str = surround(str)) # return str # end # end + # return false # end, - # function pow(io::IO, expr, prevop, config) + # function pow(io::IO, config, expr, prevop) # h, op, args = unpack(expr) # if h == :call && op == :^ # if operation(args[1]) in Latexify.trigonometric_functions # fsym = operation(args[1]) # fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") - # "$fstring^{$(decend(args[2], op))}\\left( $(join(decend.(arguments(args[1]), operation(args[1])), ", ")) \\right)" + # "$fstring^{$(descend(args[2], op))}\\left( $(join(descend.(arguments(args[1]), operation(args[1])), ", ")) \\right)" # else - # "$(decend(args[1], op))^{$(decend(args[2], Val{:NoSurround}()))}" + # "$(descend(args[1], op))^{$(descend(args[2], Val{:NoSurround}()))}" # end # end + # return false # end, - # function equals(io::IO, expr, prevop, config) + # function equals(io::IO, config, expr, prevop) # if head(expr) == :(=) - # return "$(decend(expr.args[1], expr.head)) = $(decend(expr.args[2], expr.head))" + # return "$(descend(expr.args[1], expr.head)) = $(descend(expr.args[2], expr.head))" # end # end, - # function l_funcs(io::IO, ex, prevop, config) + # function l_funcs(io::IO, config, ex, prevop) # if head(ex) == :call && startswith(string(operation(ex)), "l_") # l_func = string(operation(ex))[3:end] - # return "\\$(l_func){$(join(decend.(arguments(ex), prevop), "}{"))}" + # return "\\$(l_func){$(join(descend.(arguments(ex), prevop), "}{"))}" # end + # return false # end, ] -function build_if_else_body(io::IO, args, ifstr, elseifstr, elsestr) +function build_if_else_body(io::IO, config, args, ifstr, elseifstr, elsestr) _args = filter(x -> !(x isa LineNumberNode), args) - dargs = decend.(_args) + dargs = descend.(_args) str = if length(_args) == 2 """ $(dargs[2]) & $ifstr $(dargs[1])""" From e1348da062dc40e1fd7a2fd6cc5394d949ca538d Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Fri, 16 Apr 2021 16:26:39 +0200 Subject: [PATCH 31/40] Fix broken env inference. --- src/latexify_function.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/latexify_function.jl b/src/latexify_function.jl index 6597279a..431e47e7 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -28,7 +28,7 @@ function latexify(io::IO, args...; kwargs...) # end # _args = length(args) == 1 ? args[1] : args empty!(INSTRUCTIONS) - append!(INSTRUCTIONS, DEFAULT_INSTRUCTIONS, USER_INSTRUCTIONS, ENV_INSTRUCTIONS) + append!(INSTRUCTIONS, DEFAULT_INSTRUCTIONS, ENV_INSTRUCTIONS, USER_INSTRUCTIONS) # iterate_top_matcher(io, config, args, :_nothing) @@ -104,7 +104,7 @@ const ENV_INSTRUCTIONS = [ _default_env = x isa Tuple{AutoEnv} ? x[1].env : :_nothing env = get(config, :env, _default_env) if haskey(OUTPUTFUNCTIONS, env) - descend(io, config, OUTPUTFUNCTIONS[env](x.args), prevop) + descend(io, config, (OUTPUTFUNCTIONS[env](args),), prevop) elseif args isa Tuple{Vector, Vector} descend(io, config, (Align(hcat(args[1], args[2])),)) elseif length(args) == 1 From 86d1ee36669ffb9f24462a53dbfcef2a15c99edc Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Mon, 13 Dec 2021 13:24:13 +0100 Subject: [PATCH 32/40] Propagate `rules` along `descend`. --- src/latexify_function.jl | 63 +++++----------- src/latexraw.jl | 12 +-- src/matching_functions_test.jl | 131 +++++++++++++++++---------------- 3 files changed, 90 insertions(+), 116 deletions(-) diff --git a/src/latexify_function.jl b/src/latexify_function.jl index 431e47e7..ece523be 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -8,39 +8,10 @@ function latexify(args...; kwargs...) get(config, :render, false) && render(call_result) return call_result end -function latexify(io::IO, args...; kwargs...) - ## Let potential recipes transform the arguments. - # kwargs = merge(default_kwargs, kwargs) - # empty!(CONFIG) - # merge!(CONFIG, DEFAULT_CONFIG, default_kwargs, kwargs) - # ((_args,), _kwargs) = apply_recipe(args...; kwargs...) - # merge!(CONFIG, _kwargs) +function latexify(io::IO, args...; rules = vcat(DEFAULT_INSTRUCTIONS, ENV_INSTRUCTIONS, USER_INSTRUCTIONS), kwargs...) config = (; descend_counter = [0], DEFAULT_CONFIG..., MODULE_CONFIG..., USER_CONFIG..., default_kwargs..., kwargs...) - - - - # if env==:auto - # call_result = iterate_top_matcher(args, CONFIG) - # else - # func = OUTPUTFUNCTIONS[env] - # call_result = func(args...; CONFIG...) - # end - # _args = length(args) == 1 ? args[1] : args - empty!(INSTRUCTIONS) - append!(INSTRUCTIONS, DEFAULT_INSTRUCTIONS, ENV_INSTRUCTIONS, USER_INSTRUCTIONS) - - # iterate_top_matcher(io, config, args, :_nothing) - - # for f in env_instructions[end:-1:1] - # call_matched = f(io, config, e, prevop)::Bool - # if call_matched ## then we're done and io is populated! - # return nothing - # end - # end - # call_matched || throw(ArgumentError("No suitable environment found for $e")) - descend(io, config, args) - # latexraw(io, config, args) + descend(io, config, args, rules) end function iterate_top_matcher(io, config, args, prevop=:_nothing) @@ -98,17 +69,17 @@ const OUTPUTFUNCTIONS = (; ) const ENV_INSTRUCTIONS = [ - function _auto_environment(io::IO, config, x, prevop) + function _auto_environment(io::IO, config, x, rules, prevop) if x isa Tuple{AutoEnv} || (x isa Tuple && config[:descend_counter] == [1]) args = x isa Tuple{AutoEnv} ? x[1].args : x _default_env = x isa Tuple{AutoEnv} ? x[1].env : :_nothing env = get(config, :env, _default_env) if haskey(OUTPUTFUNCTIONS, env) - descend(io, config, (OUTPUTFUNCTIONS[env](args),), prevop) + descend(io, config, (OUTPUTFUNCTIONS[env](args),), rules, prevop) elseif args isa Tuple{Vector, Vector} - descend(io, config, (Align(hcat(args[1], args[2])),)) + descend(io, config, (Align(hcat(args[1], args[2])),), rules) elseif length(args) == 1 - descend(io, config, (Inline(args[1]),)) + descend(io, config, (Inline(args[1]),), rules) else throw(MethodError("Unspported argument to `latexify`: $args")) end @@ -116,35 +87,35 @@ const ENV_INSTRUCTIONS = [ end return false end, - function _no_env(io::IO, config, x, prevop) + function _no_env(io::IO, config, x, rules, prevop) if x isa Tuple{NoEnv} - descend(io, config, x[1].args, prevop) + descend(io, config, x[1].args, rules, prevop) return true end return false end, - function _inline(io::IO, config, x, prevop) + function _inline(io::IO, config, x, rules, prevop) if x isa Tuple{Inline} write(io, "\$") - descend(io, config, x[1].args, prevop) + descend(io, config, x[1].args, rules, prevop) write(io, "\$") return true end return false end, - function _align(io::IO, config, x, prevop) + function _align(io::IO, config, x, rules, prevop) if x isa Tuple{Align} write(io, "\\begin{align}\n") - join_matrix(io, config, x[1].args, " &= ") + join_matrix(io, config, x[1].args, " &= ", rules) write(io, "\n\\end{align}") return true end return false end, - function _equation(io::IO, config, x, prevop) + function _equation(io::IO, config, x, rules, prevop) if x isa Tuple{Equation} write(io, "\\begin{equation}\n") - descend(io, config, x[1].args, prevop) + descend(io, config, x[1].args, rules, prevop) write(io, "\n\\end{equation}") return true end @@ -152,15 +123,15 @@ const ENV_INSTRUCTIONS = [ end, ] -function join_matrix(io, config, m, delim = " & ", eol="\\\\\n") +function join_matrix(io, config, m, rules, delim = " & ", eol="\\\\\n") nrows, ncols = size(m) mime = MIME("text/latexify") for (i, row) in enumerate(eachrow(m)) for x in row[1:end-1] - descend(io, config, x) + descend(io, config, x, rules) write(io, delim) end - descend(io, config, row[end]) + descend(io, config, row[end], rules) i != nrows ? write(io, eol) : write(io, "\n") end return nothing diff --git a/src/latexraw.jl b/src/latexraw.jl index 82c5b133..df0f9ba5 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -78,10 +78,10 @@ head(ex::Expr) = ex.head unpack(x) = (head(x), operation(x), arguments(x)) -function descend(io::IO, config::NamedTuple, e, prevop=:(_nothing))::Nothing +function descend(io::IO, config::NamedTuple, e, rules::Vector{Function}, prevop=:(_nothing))::Nothing config[:descend_counter][1] += 1 - for f in INSTRUCTIONS[end:-1:1] - call_matched = f(io, config, e, prevop)::Bool + for rule in rules[end:-1:1] + call_matched = rule(io, config, e, rules, prevop)::Bool if call_matched return nothing break @@ -92,10 +92,10 @@ end surround(x) = "\\left( $x \\right)" -function join_descend(io::IO, config, args, delim; prevop=nothing) +function join_descend(io::IO, config, args, rules, delim; prevop=nothing) for arg in args[1:end-1] - descend(io, config, arg, prevop) + descend(io, config, arg, rules, rules) write(io, delim) end - descend(io, config, args[end], prevop) + descend(io, config, args[end], rules, prevop) end \ No newline at end of file diff --git a/src/matching_functions_test.jl b/src/matching_functions_test.jl index 81dedb13..c3eeca5a 100644 --- a/src/matching_functions_test.jl +++ b/src/matching_functions_test.jl @@ -1,6 +1,6 @@ -const DEFAULT_INSTRUCTIONS = [ - function report_bad_call(io::IO, config, expr, prevop) +DEFAULT_INSTRUCTIONS = [ + function report_bad_call(io::IO, config, expr, rules, prevop) println(""" Unsupported input with expr=$expr @@ -10,7 +10,7 @@ const DEFAULT_INSTRUCTIONS = [ return false return false end, - # function _unpack_args(io::IO, config, args, prevop) + # function _unpack_args(io::IO, config, args, rules, prevop) # ### If no function has claimed the tuple, unpack it and try again. # ### This is mainly to unpack the args to `latexify(args...)`. # if args isa Tuple && length(args) == 1 @@ -19,7 +19,7 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - function call(io::IO, config, expr, prevop) + function call(io::IO, config, expr, rules, prevop) if expr isa Expr && head(expr) == :call h, op, args = unpack(expr) @@ -27,18 +27,18 @@ const DEFAULT_INSTRUCTIONS = [ funcname = string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) write(io, funcname) else - descend(io, config, op) + descend(io, config, op, rules) end if length(args) >= 1 && head(args[1]) == :parameters write(io, "\\left( ") - join_descend(io, config, args[2:end], ", ") + join_descend(io, config, args[2:end], rules, ", ") write(io, "; ") - descend(io, config, args[1]) + descend(io, config, args[1], rules) write(io, " \\right)") # _arg = "\\left( $(join(descend.(args[2:end]), ", ")); $(descend(args[1])) \\right)" else write(io, "\\left( ") - join_descend(io, config, args, ", ") + join_descend(io, config, args, rules, ", ") write(io, " \\right)") # _arg = "\\left( " * join(descend.(args), ", ") * " \\right)" end @@ -46,16 +46,16 @@ const DEFAULT_INSTRUCTIONS = [ end return false end, - function _aligned(io::IO, config, expr, prevop) + function _aligned(io::IO, config, expr, rules, prevop) expr isa Tuple{T, T} where T <: AbstractArray || return false display("here") return false end, - # function _sqrt(io::IO, config, ex, prevop) + # function _sqrt(io::IO, config, ex, rules, prevop) # operation(ex) == :sqrt ? "\\$(operation(ex)){$(arguments(ex)[1])}" : nothing # return false # end, - # function _abs(io::IO, config, ex, prevop) + # function _abs(io::IO, config, ex, rules, prevop) # operation(ex) == :abs ? "\\left\\|$(arguments(ex)[1])\\right\\|" : nothing # return false # end, @@ -67,7 +67,7 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function _if(io::IO, config, ex, prevop) + # function _if(io::IO, config, ex, rules, prevop) # if ex isa Expr && head(ex) == :if # str = build_if_else_body( # ex.args, @@ -82,7 +82,7 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function _elseif(io::IO, config, ex, prevop) + # function _elseif(io::IO, config, ex, rules, prevop) # if ex isa Expr && head(ex) == :elseif # str = build_if_else_body( # ex.args, @@ -93,13 +93,13 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function _oneline_function(io::IO, config, ex, prevop) + # function _oneline_function(io::IO, config, ex, rules, prevop) # if head(ex) == :function && length(arguments(ex)) == 1 # return "$(descend(operation(ex), head(ex))) = $(descend(arguments(ex)[1], head(ex)))" # end # return false # end, - # function _return(io::IO, config, ex, prevop) + # function _return(io::IO, config, ex, rules, prevop) # head(ex) == :return && length(arguments(ex)) == 0 ? descend(operation(ex)) : nothing # return false # end, @@ -111,7 +111,7 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function _type_annotation(io::IO, config, ex, prevop) + # function _type_annotation(io::IO, config, ex, rules, prevop) # if head(ex) == :(::) # if length(arguments(ex)) == 0 # return "::$(operation(ex))" @@ -121,15 +121,15 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function _wedge(io::IO, config, ex, prevop) + # function _wedge(io::IO, config, ex, rules, prevop) # head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\wedge $(descend(arguments(ex)[1]))" : nothing # return false # end, - # function _vee(io::IO, config, ex, prevop) + # function _vee(io::IO, config, ex, rules, prevop) # head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\vee $(descend(arguments(ex)[1]))" : nothing # return false # end, - # function _negation(io::IO, config, ex, prevop) + # function _negation(io::IO, config, ex, rules, prevop) # operation(ex) == :(!) ? "\\neg $(arguments(ex)[1])" : nothing # return false # end, @@ -141,7 +141,7 @@ const DEFAULT_INSTRUCTIONS = [ # head(x) == :parameters ? join(descend.(vcat(operation(x), arguments(x))), ", ") : nothing # return false # end, - # function _indexing(io::IO, config, x, prevop) + # function _indexing(io::IO, config, x, rules, prevop) # if head(x) == :ref # if config[:index] == :subscript # return "$(operation(x))_{$(join(arguments(x), ","))}" @@ -154,7 +154,7 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function _broadcast_macro(io::IO, config, ex, prevop) + # function _broadcast_macro(io::IO, config, ex, rules, prevop) # if head(ex) == :macrocall && operation(ex) == Symbol("@__dot__") # return descend(arguments(ex)[end]) # end @@ -166,7 +166,7 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - function number(io::IO, config, x, prevop) + function number(io::IO, config, x, rules, prevop) if x isa Number try isinf(x) && write(io, "\\infty") && return true; catch; end fmt = config[:fmt] @@ -178,25 +178,28 @@ const DEFAULT_INSTRUCTIONS = [ end return false end, - # function rational_expr(io::IO, config, x, prevop) - # if operation(x) == :// - # if arguments(x)[2] == 1 - # return descend(arguments[1], prevop) - # else - # descend:($(arguments(x)[1])/$(arguments(x)[2])), prevop) - # end - # end - # return false - # end, - # function rational(io::IO, config, x, prevop) - # if x isa Rational - # str = x.den == 1 ? descend(x.num, prevop) : descend(:($(x.num)/$(x.den)), prevop) - # prevop ∈ [:*, :^] && (str = surround(str)) - # return str - # end - # return false - # end, - # function complex(io::IO, config, z, prevop) + function rational_expr(io::IO, config, x, rules, prevop) + if operation(x) == :// + if arguments(x)[2] == 1 + descend(io, config, arguments(x)[1], rules, prevop) + return true + else + descend(io, config, :($(arguments(x)[1])/$(arguments(x)[2])), rules, prevop) + return true + end + end + return false + end, + function rational(io::IO, config, x, rules, prevop) + if x isa Rational + prevop ∈ [:*, :^] && write(io, "\\left( ") + x.den == 1 ? descend(io, config, x.num, rules, prevop) : descend(io, config, :($(x.num)/$(x.den)), rules, prevop) + prevop ∈ [:*, :^] && write(io, " \\right)") + return true + end + return false + end, + # function complex(io::IO, config, z, rules, prevop) # if z isa Complex # str = "$(descend(z.re))$(z.im < 0 ? "-" : "+" )$(descend(abs(z.im)))\\textit{i}" # prevop ∈ [:*, :^] && (str = surround(str)) @@ -204,17 +207,17 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function _missing(io::IO, config, x, prevop) + # function _missing(io::IO, config, x, rules, prevop) # if ismissing(x) # "\\textrm{NA}" # end # return false # end, - # function _nothing(io::IO, config, x, prevop) + # function _nothing(io::IO, config, x, rules, prevop) # x === nothing ? "" : nothing # return false # end, - function symbol(io::IO, config, sym, _) + function symbol(io::IO, config, sym, rules, _) if sym isa Symbol str = string(sym == :Inf ? :∞ : sym) str = convert_subscript(str) @@ -224,7 +227,7 @@ const DEFAULT_INSTRUCTIONS = [ end return false end, - # function _char(io::IO, config, c, args...) + # function _char(io::IO, config, c, rules, args...) # c isa Char ? string(c) : nothing # return false # end, @@ -253,7 +256,7 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function parse_string(io::IO, config, str, prevop) + # function parse_string(io::IO, config, str, rules, prevop) # if str isa AbstractString # try # ex = Meta.parse(str) @@ -287,58 +290,58 @@ const DEFAULT_INSTRUCTIONS = [ # str isa LaTeXString ? str.s : nothing # return false # end, - # function _tuple_expr(io::IO, config, expr, prevop) + # function _tuple_expr(io::IO, config, expr, rules, prevop) # head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing # return false # end, - # function strip_broadcast_dot(io::IO, config, expr, prevop) + # function strip_broadcast_dot(io::IO, config, expr, rules, prevop) # h, op, args = unpack(expr) # if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') # return string(descend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) # end # return false # end, - # function strip_broadcast_dot_call(io::IO, config, expr, prevop) + # function strip_broadcast_dot_call(io::IO, config, expr, rules, prevop) # h, op, args = unpack(expr) # if expr isa Expr && config[:strip_broadcast] && h == :. # return descend(Expr(:call, op, args[1].args...), prevop) # end # return false # end, - # function plusminus(io::IO, config, expr, prevop) + # function plusminus(io::IO, config, expr, rules, prevop) # h, op, args = unpack(expr) # if h == :call && op == :± # return "$(descend(args[1], op)) \\pm $(descend(args[2], op))" # end # return false # end, - function division(io::IO, config, expr, prevop) + function division(io::IO, config, expr, rules, prevop) h, op, args = unpack(expr) if h == :call && op == :/ write(io, "\\frac{") - descend(io, config, args[1], op) + descend(io, config, args[1], rules, op) write(io, "}{") - descend(io, config, args[2], op) + descend(io, config, args[2], rules, op) write(io, "}") # "\\frac{$(descend(args[1], op))}{$(descend(args[2], op))}" return true end return false end, - function multiplication(io::IO, config, expr, prevop) + function multiplication(io::IO, config, expr, rules, prevop) h, op, args = unpack(expr) if h == :call && op == :* # join(descend.(args, op), "$(config[:mulsym])") - join_descend(io, config, args, config[:mulsym]) + join_descend(io, config, args, rules, config[:mulsym]) return true end return false end, - function addition(io::IO, config, expr, prevop) + function addition(io::IO, config, expr, rules, prevop) h, op, args = unpack(expr) if h == :call && op == :+ prevop ∈ [:*, :^] && write(io, "\\left( ") - str = join_descend(io, config, args, " + ") + str = join_descend(io, config, args, rules, " + ") # str = replace(str, "+ -"=>"-") # prevop ∈ [:*, :^] && (str = surround(str)) prevop ∈ [:*, :^] && write(io, " \\right)") @@ -347,7 +350,7 @@ const DEFAULT_INSTRUCTIONS = [ end return false end, - # function subtraction(io::IO, config, expr, prevop) + # function subtraction(io::IO, config, expr, rules, prevop) # # this one is so gnarly because it tries to fix stuff like - - or -(-(x-y)) # # -(x) is also a bit different to -(x, y) which does not make things simpler # h, op, args = unpack(expr) @@ -380,7 +383,7 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function pow(io::IO, config, expr, prevop) + # function pow(io::IO, config, expr, rules, prevop) # h, op, args = unpack(expr) # if h == :call && op == :^ # if operation(args[1]) in Latexify.trigonometric_functions @@ -393,12 +396,13 @@ const DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function equals(io::IO, config, expr, prevop) - # if head(expr) == :(=) - # return "$(descend(expr.args[1], expr.head)) = $(descend(expr.args[2], expr.head))" + # write(io, " = ") + # descend(io, config, args[1], h) + # return true # end + # return false # end, - # function l_funcs(io::IO, config, ex, prevop) + # function l_funcs(io::IO, config, ex, rules, prevop) # if head(ex) == :call && startswith(string(operation(ex)), "l_") # l_func = string(operation(ex))[3:end] # return "\\$(l_func){$(join(descend.(arguments(ex), prevop), "}{"))}" @@ -407,7 +411,6 @@ const DEFAULT_INSTRUCTIONS = [ # end, ] - function build_if_else_body(io::IO, config, args, ifstr, elseifstr, elsestr) _args = filter(x -> !(x isa LineNumberNode), args) dargs = descend.(_args) From d9d3b8916a1196b9cdf162fa9e4c853a9dafb55f Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Mon, 13 Dec 2021 14:17:56 +0100 Subject: [PATCH 33/40] Add `TracedIO` for better testing. Allows for tracing the call path along the different latexify rules. --- src/Latexify.jl | 1 + src/TracedIO.jl | 19 +++++++++++++++++++ src/latexify_function.jl | 13 ++++++++++++- src/latexraw.jl | 2 ++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/TracedIO.jl diff --git a/src/Latexify.jl b/src/Latexify.jl index a01d33fa..797c3a80 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -46,6 +46,7 @@ include("mdtext.jl") include("md.jl") include("utils.jl") +include("TracedIO.jl") include("config.jl") include("latexraw_recipes.jl") diff --git a/src/TracedIO.jl b/src/TracedIO.jl new file mode 100644 index 00000000..1ce32965 --- /dev/null +++ b/src/TracedIO.jl @@ -0,0 +1,19 @@ + +struct TracedIO{T} <: IO + trace::Vector{Function} + io::T +end +Base.write(x::TracedIO, s::String; kwargs...) = Base.write(x.io, s; kwargs...) + +struct TracedResult{T} + trace::Vector{Function} + str::T +end + +function Base.show(io::IO, tio::TracedResult) + write(io, "Latexify TracedResult\n") + write(io, "\nwith call trace:\n") + write(io, join(string.(typeof.(tio.trace)), "\n")) + write(io, "\n\nand result\n") + show(io, tio.str) +end \ No newline at end of file diff --git a/src/latexify_function.jl b/src/latexify_function.jl index ece523be..1cc6cf97 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -9,7 +9,18 @@ function latexify(args...; kwargs...) return call_result end -function latexify(io::IO, args...; rules = vcat(DEFAULT_INSTRUCTIONS, ENV_INSTRUCTIONS, USER_INSTRUCTIONS), kwargs...) +function latextrace(args...; kwargs...) + tio = TracedIO(Function[], IOBuffer(; append=true, read=true)) + config = (; DEFAULT_CONFIG..., MODULE_CONFIG..., USER_CONFIG..., default_kwargs..., kwargs...) + latexify(tio, args...; kwargs...) + call_result = LaTeXString(read(tio.io, String)) + COPY_TO_CLIPBOARD && clipboard(call_result) + AUTO_DISPLAY && display(call_result) + get(config, :render, false) && render(call_result) + return TracedResult(tio.trace, call_result) +end + +function latexify(io::IO, args...; rules = vcat(DEFAULT_INSTRUCTIONS, USER_INSTRUCTIONS, ENV_INSTRUCTIONS, USER_INSTRUCTIONS), kwargs...) config = (; descend_counter = [0], DEFAULT_CONFIG..., MODULE_CONFIG..., USER_CONFIG..., default_kwargs..., kwargs...) descend(io, config, args, rules) end diff --git a/src/latexraw.jl b/src/latexraw.jl index df0f9ba5..9d563dcb 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -80,7 +80,9 @@ unpack(x) = (head(x), operation(x), arguments(x)) function descend(io::IO, config::NamedTuple, e, rules::Vector{Function}, prevop=:(_nothing))::Nothing config[:descend_counter][1] += 1 + io isa TracedIO && push!(io.trace, x->nothing) for rule in rules[end:-1:1] + io isa TracedIO && (io.trace[end] = rule) ## Inefficient to overwrite so much but should I care? call_matched = rule(io, config, e, rules, prevop)::Bool if call_matched return nothing From e6d331057cb2e6af5d6b3112d9dc3590383949dc Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Mon, 5 Sep 2022 18:27:42 +0200 Subject: [PATCH 34/40] Greatly improve parity with main. Add matchers, tests and fix bugs. --- src/Latexify.jl | 4 +- src/latexify_function.jl | 106 ++++- src/latexraw.jl | 11 +- src/matching_functions_test.jl | 810 ++++++++++++++++++++------------- src/recipes.jl | 37 ++ test/latexalign_test.jl | 20 +- test/latexarray_test.jl | 52 +-- test/latexbracket_test.jl | 8 +- test/latexequation_test.jl | 6 +- test/latexify_test.jl | 2 +- test/latexraw_test.jl | 100 ++-- test/latextabular_test.jl | 132 +++--- test/macros.jl | 6 +- 13 files changed, 783 insertions(+), 511 deletions(-) diff --git a/src/Latexify.jl b/src/Latexify.jl index 797c3a80..e2cb46e0 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -17,12 +17,12 @@ export latexequation, latexarray, latexalign, latexraw, latexinline, latextabula export StyledNumberFormatter, FancyNumberFormatter -COPY_TO_CLIPBOARD = false +const COPY_TO_CLIPBOARD = false function copy_to_clipboard(x::Bool) global COPY_TO_CLIPBOARD = x end -AUTO_DISPLAY = false +const AUTO_DISPLAY = false function auto_display(x::Bool) global AUTO_DISPLAY = x end diff --git a/src/latexify_function.jl b/src/latexify_function.jl index 1cc6cf97..0d68178b 100644 --- a/src/latexify_function.jl +++ b/src/latexify_function.jl @@ -20,9 +20,9 @@ function latextrace(args...; kwargs...) return TracedResult(tio.trace, call_result) end -function latexify(io::IO, args...; rules = vcat(DEFAULT_INSTRUCTIONS, USER_INSTRUCTIONS, ENV_INSTRUCTIONS, USER_INSTRUCTIONS), kwargs...) +function latexify(io::IO, args...; rules = vcat(DEFAULT_INSTRUCTIONS, reduce(vcat, values(MODULE_INSTRUCTIONS); init=Function[]), USER_INSTRUCTIONS, ENV_INSTRUCTIONS, USER_INSTRUCTIONS), kwargs...) config = (; descend_counter = [0], DEFAULT_CONFIG..., MODULE_CONFIG..., USER_CONFIG..., default_kwargs..., kwargs...) - descend(io, config, args, rules) + descend(io, args, config, rules) end function iterate_top_matcher(io, config, args, prevop=:_nothing) @@ -41,11 +41,26 @@ apply_recipe(args...; kwargs...) = (args, kwargs) abstract type AbstractLatexifyEnvironment end abstract type AbstractLatexEnvironment <: AbstractLatexifyEnvironment end -for T in [:Inline, :Equation, :Align, :Bracket, :Table, :NoEnv, :Aligned, :Array] +for T in [:Inline, :Equation, :Bracket, :Table, :NoEnv, :Aligned, :Array] @eval(struct $T <: AbstractLatexEnvironment args end) end +for C in [:Inline, :Equation, :Bracket] + @eval $C(x1, x2) = $C(x1 .=> x2) +end +Table(d::Dict) = Table(hcat(collect(keys(d)), collect(values(d)))) +struct Align{T} <: AbstractLatexEnvironment + args::Matrix{T} +end +Align(t1::Tuple, t2::Tuple) = Align(reduce(hcat, (collect(t1), collect(t2)))) +Align(t::Tuple{Union{Tuple, Vector}, Union{Tuple, Vector}}) = Align(reduce(hcat, collect.(t))) +# Align(t::Tuple{Tuple}) = Align(first(t)) +Align(v1::Vector, v2::Vector) = Align([v1 v2]) +# Align(t::Tuple{Vector{<:AbstractString}}) = Align(first(t)) +Align(v::Vector{<:AbstractString}) = Align(permutedims(reduce(hcat, split.(v, '=')))) +Align(v::Vector{<:Pair}) = Align(hcat(first.(v), last.(v))) +Align(t::Tuple{T}) where T = Align(first(t)) struct AutoEnv <: AbstractLatexEnvironment args env::Symbol @@ -80,17 +95,21 @@ const OUTPUTFUNCTIONS = (; ) const ENV_INSTRUCTIONS = [ - function _auto_environment(io::IO, config, x, rules, prevop) + function _auto_environment(io::IO, x, config, rules, prevop) if x isa Tuple{AutoEnv} || (x isa Tuple && config[:descend_counter] == [1]) args = x isa Tuple{AutoEnv} ? x[1].args : x _default_env = x isa Tuple{AutoEnv} ? x[1].env : :_nothing + if haskey(config, :env) && !haskey(OUTPUTFUNCTIONS, config.env) + throw(ArgumentError("Unsupported env specification :$(config.env)\nSupported envs are $(keys(OUTPUTFUNCTIONS))")) + end + env = get(config, :env, _default_env) if haskey(OUTPUTFUNCTIONS, env) - descend(io, config, (OUTPUTFUNCTIONS[env](args),), rules, prevop) + descend(io, (OUTPUTFUNCTIONS[env](args...),), config, rules, prevop) elseif args isa Tuple{Vector, Vector} - descend(io, config, (Align(hcat(args[1], args[2])),), rules) + descend(io, (Align(hcat(args[1], args[2])),), config, rules) elseif length(args) == 1 - descend(io, config, (Inline(args[1]),), rules) + descend(io, (Inline(args[1]),), config, rules) else throw(MethodError("Unspported argument to `latexify`: $args")) end @@ -98,51 +117,90 @@ const ENV_INSTRUCTIONS = [ end return false end, - function _no_env(io::IO, config, x, rules, prevop) + function _no_env(io::IO, x, config, rules, prevop) if x isa Tuple{NoEnv} - descend(io, config, x[1].args, rules, prevop) + # for arg in x[1].args + descend(io, x[1].args, config, rules, prevop) + # descend(io, arg, config, rules, prevop) + # end return true end return false end, - function _inline(io::IO, config, x, rules, prevop) + function _inline(io::IO, x, config, rules, prevop) if x isa Tuple{Inline} write(io, "\$") - descend(io, config, x[1].args, rules, prevop) + descend(io, x[1].args, config, rules, prevop) write(io, "\$") return true end return false end, - function _align(io::IO, config, x, rules, prevop) + function _table(io::IO, x, config, rules, prevop) + if x isa Tuple{Table} + adjustment = get(config, :adjustment, "l") + elements = get(config, :latex, true) ? map(y->(Inline(y),), x[1].args) : x[1].args + get(config, :transpose, false) && (elements = permutedims(elements)) + if haskey(config, :head) + elements=vcat(permutedims(vec(config[:head])), elements) + end + if haskey(config, :side) + elements=hcat(vec(config[:side]), elements) + end + columns = size(elements, 2) + write(io, "\\begin{tabular}{$(string(adjustment)^columns)}\n") + get(config, :booktabs, false) && write(io, "\\toprule\n") + join_matrix(io, elements, config, rules, " & ") + get(config, :booktabs, false) && write(io, "\\bottomrule\n") + write(io, "\\end{tabular}") + return true + end + return false + end, + function _align(io::IO, x, config, rules, prevop) if x isa Tuple{Align} - write(io, "\\begin{align}\n") - join_matrix(io, config, x[1].args, " &= ", rules) - write(io, "\n\\end{align}") + write(io, "\\begin{align$(get(config, :starred, false) ? '*' : "")}\n") + join_matrix(io, x[1].args, config, rules, get(config, :separator, " =& ")) + write(io, "\\end{align$(get(config, :starred, false) ? '*' : "")}\n") return true end return false end, - function _equation(io::IO, config, x, rules, prevop) + function _bracket(io::IO, x, config, rules, prevop) + if x isa Tuple{Bracket} + write(io, "\\[\n") + descend(io, x[1].args, config, rules, prevop) + write(io, "\n\\]") + return true + end + return false + end, + function _equation(io::IO, x, config, rules, prevop) if x isa Tuple{Equation} - write(io, "\\begin{equation}\n") - descend(io, config, x[1].args, rules, prevop) - write(io, "\n\\end{equation}") + write(io, "\\begin{equation$(get(config, :starred, false) ? '*' : "")}\n") + descend(io, x[1].args, config, rules, prevop) + write(io, "\n\\end{equation$(get(config, :starred, false) ? '*' : "")}") return true end return false end, + # function _mdtable(io::IO, x ,config, rules, prevop) + # if x isa Tuple{MDTable} + + # end + # return false + # end ] -function join_matrix(io, config, m, rules, delim = " & ", eol="\\\\\n") +join_matrix(io, m, config, rules, delim::Union{String, Char, Symbol}, eol) = join_matrix(io, m, config, rules, fill(delim, size(m,1))) +function join_matrix(io, m, config, rules, delim = fill(" & ", size(m, 1)), eol=" \\\\\n") nrows, ncols = size(m) - mime = MIME("text/latexify") for (i, row) in enumerate(eachrow(m)) for x in row[1:end-1] - descend(io, config, x, rules) - write(io, delim) + descend(io, x, config, rules) + write(io, delim[i]) end - descend(io, config, row[end], rules) + descend(io, row[end], config, rules) i != nrows ? write(io, eol) : write(io, "\n") end return nothing diff --git a/src/latexraw.jl b/src/latexraw.jl index 9d563dcb..f4d1bb8c 100644 --- a/src/latexraw.jl +++ b/src/latexraw.jl @@ -1,5 +1,6 @@ const INSTRUCTIONS = Function[] const USER_INSTRUCTIONS = Function[] +const MODULE_INSTRUCTIONS = Dict{Symbol, Vector{Function}}() const USER_ENV_INSTRUCTIONS = Function[] # function latexraw(io::IO, config::NamedTuple, expr) @@ -78,12 +79,12 @@ head(ex::Expr) = ex.head unpack(x) = (head(x), operation(x), arguments(x)) -function descend(io::IO, config::NamedTuple, e, rules::Vector{Function}, prevop=:(_nothing))::Nothing +function descend(io::IO, e, config::NamedTuple, rules::Vector{Function}, prevop=:(_nothing))::Nothing config[:descend_counter][1] += 1 io isa TracedIO && push!(io.trace, x->nothing) for rule in rules[end:-1:1] io isa TracedIO && (io.trace[end] = rule) ## Inefficient to overwrite so much but should I care? - call_matched = rule(io, config, e, rules, prevop)::Bool + call_matched = rule(io, e, config, rules, prevop)::Bool if call_matched return nothing break @@ -94,10 +95,10 @@ end surround(x) = "\\left( $x \\right)" -function join_descend(io::IO, config, args, rules, delim; prevop=nothing) +function join_descend(io::IO, args, config, rules, delim; prevop=nothing) for arg in args[1:end-1] - descend(io, config, arg, rules, rules) + descend(io, arg, config, rules, prevop) write(io, delim) end - descend(io, config, args[end], rules, prevop) + descend(io, args[end], config, rules, prevop) end \ No newline at end of file diff --git a/src/matching_functions_test.jl b/src/matching_functions_test.jl index c3eeca5a..f3039598 100644 --- a/src/matching_functions_test.jl +++ b/src/matching_functions_test.jl @@ -1,6 +1,6 @@ -DEFAULT_INSTRUCTIONS = [ - function report_bad_call(io::IO, config, expr, rules, prevop) +DEFAULT_INSTRUCTIONS = Function[ + function report_bad_call(io::IO, expr, config, rules, prevop) println(""" Unsupported input with expr=$expr @@ -10,16 +10,16 @@ DEFAULT_INSTRUCTIONS = [ return false return false end, - # function _unpack_args(io::IO, config, args, rules, prevop) - # ### If no function has claimed the tuple, unpack it and try again. - # ### This is mainly to unpack the args to `latexify(args...)`. - # if args isa Tuple && length(args) == 1 - # descend(io, config, args[1], prevop) - # return true - # end - # return false - # end, - function call(io::IO, config, expr, rules, prevop) + function _unpack_args(io::IO, args, config, rules, prevop) + ### If no function has claimed the tuple, unpack it and try again. + ### This is mainly to unpack the args to `latexify(args...)`. + if args isa Tuple && length(args) == 1 + descend(io, args[1], config, rules, prevop) + return true + end + return false + end, + function call(io::IO, expr, config, rules, prevop) if expr isa Expr && head(expr) == :call h, op, args = unpack(expr) @@ -27,18 +27,18 @@ DEFAULT_INSTRUCTIONS = [ funcname = string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) write(io, funcname) else - descend(io, config, op, rules) + descend(io, op, config, rules) end if length(args) >= 1 && head(args[1]) == :parameters write(io, "\\left( ") - join_descend(io, config, args[2:end], rules, ", ") + join_descend(io, args[2:end], config, rules, ", ") write(io, "; ") - descend(io, config, args[1], rules) + descend(io, args[1], config, rules) write(io, " \\right)") # _arg = "\\left( $(join(descend.(args[2:end]), ", ")); $(descend(args[1])) \\right)" else write(io, "\\left( ") - join_descend(io, config, args, rules, ", ") + !isempty(args) && join_descend(io, args, config, rules, ", ") write(io, " \\right)") # _arg = "\\left( " * join(descend.(args), ", ") * " \\right)" end @@ -46,179 +46,229 @@ DEFAULT_INSTRUCTIONS = [ end return false end, - function _aligned(io::IO, config, expr, rules, prevop) - expr isa Tuple{T, T} where T <: AbstractArray || return false - display("here") - return false + function _infix_operator(io, ex, config, rules, prevop) + ex isa Expr && head(ex) == :call && operation(ex) isa Symbol && Base.isbinaryoperator(operation(ex)) || return false + descend(io, arguments(ex)[1], config, rules, operation(ex)) + write(io, " ") + descend(io, operation(ex), config, rules, operation(ex)) + write(io, " ") + descend(io, arguments(ex)[2], config, rules, operation(ex)) + return true end, - # function _sqrt(io::IO, config, ex, rules, prevop) - # operation(ex) == :sqrt ? "\\$(operation(ex)){$(arguments(ex)[1])}" : nothing - # return false - # end, - # function _abs(io::IO, config, ex, rules, prevop) - # operation(ex) == :abs ? "\\left\\|$(arguments(ex)[1])\\right\\|" : nothing - # return false - # end, - # function _single_comparison(io::IO, config, ex, args...) - # if operation(ex) ∈ keys(comparison_operators) && length(arguments(ex)) == 2 - # str = "$(arguments(ex)[1]) $(comparison_operators[operation(ex)]) $(arguments(ex)[2])" - # str = "\\left( $str \\right)" - # return str - # end - # return false - # end, - # function _if(io::IO, config, ex, rules, prevop) - # if ex isa Expr && head(ex) == :if - # str = build_if_else_body( - # ex.args, - # config[:ifstr], - # config[:elseifstr], - # config[:elsestr] - # ) - # return """ - # \\begin{cases} - # $str - # \\end{cases}""" - # end - # return false - # end, - # function _elseif(io::IO, config, ex, rules, prevop) - # if ex isa Expr && head(ex) == :elseif - # str = build_if_else_body( - # ex.args, - # config[:elseifstr], - # config[:elseifstr], - # config[:elsestr] - # ) - # end - # return false - # end, - # function _oneline_function(io::IO, config, ex, rules, prevop) - # if head(ex) == :function && length(arguments(ex)) == 1 - # return "$(descend(operation(ex), head(ex))) = $(descend(arguments(ex)[1], head(ex)))" - # end - # return false - # end, - # function _return(io::IO, config, ex, rules, prevop) - # head(ex) == :return && length(arguments(ex)) == 0 ? descend(operation(ex)) : nothing - # return false - # end, - # function _chained_comparisons(io::IO, config, ex, _...) - # if head(ex) == :comparison && Symbol.(arguments(ex)[1:2:end]) ⊆ keys(comparison_operators) - # str = join([isodd(i) ? "$var" : comparison_operators[Symbol(var)] for (i, var) in enumerate(descend.(vcat(operation(ex), arguments(ex))))], " ") - # str = "\\left( $str \\right)" - # return str - # end - # return false - # end, - # function _type_annotation(io::IO, config, ex, rules, prevop) - # if head(ex) == :(::) - # if length(arguments(ex)) == 0 - # return "::$(operation(ex))" - # elseif length(arguments(ex)) == 1 - # return "$(operation(ex))::$(arguments(ex)[1])" - # end - # end - # return false - # end, - # function _wedge(io::IO, config, ex, rules, prevop) - # head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\wedge $(descend(arguments(ex)[1]))" : nothing - # return false - # end, - # function _vee(io::IO, config, ex, rules, prevop) - # head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\vee $(descend(arguments(ex)[1]))" : nothing - # return false - # end, - # function _negation(io::IO, config, ex, rules, prevop) - # operation(ex) == :(!) ? "\\neg $(arguments(ex)[1])" : nothing - # return false - # end, - # function _kw(io::IO, config, x, args...) - # head(x) == :kw ? "$(descend(operation(x))) = $(descend(arguments(x)[1]))" : nothing - # return false - # end, - # function _parameters(io::IO, config, x, args...) - # head(x) == :parameters ? join(descend.(vcat(operation(x), arguments(x))), ", ") : nothing - # return false - # end, - # function _indexing(io::IO, config, x, rules, prevop) - # if head(x) == :ref - # if config[:index] == :subscript - # return "$(operation(x))_{$(join(arguments(x), ","))}" - # elseif config[:index] == :bracket - # argstring = join(descend.(arguments(x)), ", ") - # return "$(descend(operation(x)))\\left[$argstring\\right]" - # else - # throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket")) - # end - # end - # return false - # end, - # function _broadcast_macro(io::IO, config, ex, rules, prevop) - # if head(ex) == :macrocall && operation(ex) == Symbol("@__dot__") - # return descend(arguments(ex)[end]) - # end - # return false - # end, - # function _block(io::IO, config, x, args...) - # if head(x) == :block - # return descend(vcat(operation(x), arguments(x))[end]) - # end - # return false - # end, - function number(io::IO, config, x, rules, prevop) + function _aligned_tuple(io::IO, expr, config, rules, prevop) + expr isa Tuple{Vararg{Pair}} || return false + descend(io, NamedTuple(expr), config, rules, prevop) + return true + end, + function _aligned(io::IO, expr, config, rules, prevop) + # expr isa Tuple{T, T} where T <: AbstractArray || return false + # expr isa Tuple{Pair} || return false + expr isa NamedTuple || return false + write(io, "\\begin{aligned}\n") + p_expr = pairs(expr) + for p in p_expr[1:end-1] + descend(io, p, config, rules, nothing) + write(io, " \\\\\n") + end + descend(io, p_expr[end], config, rules, nothing) + write(io, "\n") + write(io, "\\end{aligned}\n") + return true + end, + function _pair(io::IO, expr, config, rules, prevop) + expr isa Pair || return false + descend(io, expr.first, config, rules, :(=)) + write(io, get(config, :separator, " =& ")) + descend(io, expr.second, config, rules, :(=)) + return true + end, + function _sqrt(io::IO, ex, config, rules, prevop) + operation(ex) == :sqrt || return false + write(io, "\\$(operation(ex)){") + descend(io, arguments(ex)[1], config, rules, :sqrt) + write(io, "}") + return true + return false + end, + function _abs(io::IO, ex, config, rules, prevop) + operation(ex) == :abs || return false + write(io, "\\left\\| ") + descend(io, arguments(ex)[1], config, rules, :abs) + write(io, " \\right\\|") + return true + end, + function _single_comparison(io::IO, ex, config, rules, prevop) + operation(ex) ∈ keys(comparison_operators) && length(arguments(ex)) == 2 || return false + # write(io, "\\left( ") + join_descend(io, [arguments(ex)[1], operation(ex), arguments(ex)[2]], config, rules, " "; prevop) + # write(io, " \\right)") + return true + end, + function _if(io::IO, ex, config, rules, prevop) + ex isa Expr && head(ex) == :if || return false + write(io, "\n\\begin{cases}") + build_if_else_body(io, ex.args, config, rules, nothing) + write(io, "\n\\end{cases}\n") + return true + end, + function _elseif(io::IO, ex, config, rules, prevop) + ex isa Expr && head(ex) == :elseif || return false + build_if_else_body(io, ex.args, config, rules, prevop; ifstr=get(config, :elseifstr, "elseif")) + return true + end, + function _oneline_function(io::IO, ex, config, rules, prevop) + head(ex) == :function && length(arguments(ex)) == 1 || return false + join_descend(io, [operation(ex), :(=), arguments(ex)[1]], config, rules, " ") + return true + end, + function _return(io::IO, ex, config, rules, prevop) + head(ex) == :return && length(arguments(ex)) == 0 || return false + descend(io, operation(ex), config, rules, nothing) + return true + end, + function _chained_comparisons(io::IO, ex, config, rules, prevop) + head(ex) == :comparison && Symbol.(arguments(ex)[1:2:end]) ⊆ keys(comparison_operators) || return false + prevop ∈ [:*, :^, :+, :-] && write(io, "\\left( ") + join_descend(io, ex.args, config, rules, " "; prevop=nothing) + prevop ∈ [:*, :^, :+, :-] && write(io, " \\right)") + return true + end, + function _type_annotation(io::IO, ex, config, rules, prevop) + head(ex) == :(::) || return false + if length(arguments(ex)) == 0 + write(io, "::") + descend(io, :(l_mathrm($(operation(ex)))), config, rules, nothing) + elseif length(arguments(ex)) == 1 + descend(io, operation(ex), config, rules, nothing) + write(io, "::") + descend(io, :(l_mathrm($(arguments(ex)[1]))), config, rules, nothing) + end + return true + end, + function _wedge(io::IO, ex, config, rules, prevop) + head(ex) == :(&&) && length(arguments(ex)) == 1 || return false + descend(io, operation(ex), config, rules, nothing) + write(io, " \\wedge ") + descend(io, arguments(ex)[1], config, rules, nothing) + return true + end, + function _vee(io::IO, ex, config, rules, prevop) + head(ex) == :(||) && length(arguments(ex)) == 1 || return false + descend(io, operation(ex), config, rules, nothing) + write(io, " \\vee ") + descend(io, arguments(ex)[1], config, rules, nothing) + return true + end, + function _negation(io::IO, ex, config, rules, prevop) + operation(ex) == :(!) || return false + write(io, "\\neg ") + descend(io, arguments(ex)[1], config, rules, nothing) + return true + end, + function _kw(io::IO, ex, config, rules, prevop) + head(ex) == :kw || return false + descend(io, operation(ex), config, rules, nothing) + write(io, " = ") + descend(io, arguments(ex)[1], config, rules, nothing) + return true + end, + function _parameters(io::IO, x, config, rules, prevop) + head(x) == :parameters || return false + join_descend(io, vcat(operation(x), arguments(x)), config, rules, ", ") + # join(descend.(vcat(operation(x), arguments(x))), ", ") : nothing + return true + end, + function _indexing(io::IO, x, config, rules, prevop) + head(x) == :ref || return false + if config[:index] == :subscript + descend(io, operation(x), config, rules, nothing) + write(io, "_{") + join_descend(io, arguments(x), config, rules, ",") + write(io, "}") + elseif config[:index] == :bracket + descend(io, operation(x), config, rules, nothing) + write(io, "\\left[") + join_descend(io, arguments(x), config, rules, ", ") + write(io, "\\right]") + else + throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket")) + end + return true + end, + function _broadcast_macro(io::IO, ex, config, rules, prevop) + head(ex) == :macrocall && operation(ex) == Symbol("@__dot__") || return false + descend(io, arguments(ex)[end], config, rules, prevop) + return true + end, + function _block(io::IO, x, config, rules, prevop) + head(x) == :block || return false + descend(io, vcat(operation(x), arguments(x))[end], config, rules, prevop) + # _args = filter(x -> !(x isa LineNumberNode), x.args) + # # write(io, "\\left\\{") + # write(io, "\\begin{array}{c}") + # join_descend(io, _args, config, rules, "\\\\\n"; prevop) + # write(io, "\\end{array}") + return true + end, + function number(io::IO, x, config, rules, prevop) if x isa Number - try isinf(x) && write(io, "\\infty") && return true; catch; end + if hasmethod(isinf, Tuple{typeof(x)}) && isinf(x) + write(io, "\\infty") + return true + end fmt = config[:fmt] fmt isa String && (fmt = PrintfNumberFormatter(fmt)) str = string(fmt(x)) - sign(x) == -1 && prevop == :^ && (str = surround(str)) write(io, str) return true end return false end, - function rational_expr(io::IO, config, x, rules, prevop) + function rational_expr(io::IO, x, config, rules, prevop) if operation(x) == :// if arguments(x)[2] == 1 - descend(io, config, arguments(x)[1], rules, prevop) + descend(io, arguments(x)[1], config, rules, prevop) return true else - descend(io, config, :($(arguments(x)[1])/$(arguments(x)[2])), rules, prevop) + descend(io, :($(arguments(x)[1])/$(arguments(x)[2])), config, rules, prevop) return true end end return false end, - function rational(io::IO, config, x, rules, prevop) + function rational(io::IO, x, config, rules, prevop) if x isa Rational prevop ∈ [:*, :^] && write(io, "\\left( ") - x.den == 1 ? descend(io, config, x.num, rules, prevop) : descend(io, config, :($(x.num)/$(x.den)), rules, prevop) + x.den == 1 ? descend(io, x.num, config, rules, nothing) : descend(io, :($(x.num)/$(x.den)), config, rules, nothing) prevop ∈ [:*, :^] && write(io, " \\right)") return true end return false end, - # function complex(io::IO, config, z, rules, prevop) - # if z isa Complex - # str = "$(descend(z.re))$(z.im < 0 ? "-" : "+" )$(descend(abs(z.im)))\\textit{i}" - # prevop ∈ [:*, :^] && (str = surround(str)) - # return str - # end - # return false - # end, - # function _missing(io::IO, config, x, rules, prevop) - # if ismissing(x) - # "\\textrm{NA}" - # end - # return false - # end, - # function _nothing(io::IO, config, x, rules, prevop) - # x === nothing ? "" : nothing - # return false - # end, - function symbol(io::IO, config, sym, rules, _) + function complex(io::IO, z, config, rules, prevop) + if z isa Complex + prevop ∈ [:*, :^] && write(io, "\\left( ") + descend(io, z.re, config, rules, nothing) + write(io, z.im < 0 ? "-" : "+") + descend(io, abs(z.im), config, rules, nothing) + write(io, "\\textit{i}") + prevop ∈ [:*, :^] && write(io, " \\right)") + return true + end + return false + end, + function _missing(io::IO, x, config, rules, prevop) + ismissing(x) || return false + write(io, get(config, :missingstring, "\\textrm{NA}")) + return true + end, + function _nothing(io::IO, x, config, rules, prevop) + x === nothing || return false + return true + end, + function symbol(io::IO, sym, config, rules, _) if sym isa Symbol + sym = sym ∈ keys(comparison_operators) ? comparison_operators[sym] : sym str = string(sym == :Inf ? :∞ : sym) str = convert_subscript(str) config[:convert_unicode] && (str = unicode2latex(str)) @@ -227,27 +277,67 @@ DEFAULT_INSTRUCTIONS = [ end return false end, - # function _char(io::IO, config, c, rules, args...) - # c isa Char ? string(c) : nothing - # return false - # end, - # function array(io::IO, config, x, args...) - # x isa AbstractArray ? _latexarray(x) : nothing - # return false - # end, - # function tuple(io::IO, config, x, args...) - # x isa Tuple ? _latexarray(x) : nothing - # return false - # end, - # function vect_exp(io::IO, config, x, args...) - # head(x) ∈ [:vect, :vcat] ? _latexarray(expr_to_array(x)) : nothing - # return false - # end, - # function hcat_exp(io::IO, config, x, args...) - # # head(x)==:hcat ? _latexarray(permutedims(vcat(operation(x), arguments(x)))) : nothing - # head(x)==:hcat ? _latexarray(expr_to_array(x)) : nothing - # return false - # end, + function _char(io::IO, c, config, rules, prevop) + c isa Char || return false + descend(io, Symbol(c), config, rules, prevop) + return true + end, + function array(io::IO, arr, config, rules, prevop) + if arr isa AbstractArray + adjustment = get(config, :adjustment, "c") + transpose = get(config, :transpose, false) + double_linebreak = get(config, :double_linebreak, false) + starred = get(config, :starred, false) + + transpose && (arr = permutedims(arr)) + rows = first(size(arr)) + columns = length(size(arr)) > 1 ? size(arr)[2] : 1 + + eol = double_linebreak ? " \\\\\\\\\n" : " \\\\\n" + write(io, get(config, :array_left, "\\left[\n")) + write(io, "\\begin{array}{" * "$(adjustment)"^columns * "}\n") + + for i=1:rows, j=1:columns + descend(io, arr[i,j], config, rules, nothing) + write(io, j==columns ? eol : " & ") + end + + write(io, "\\end{array}") + write(io, get(config, :array_right, "\n\\right]")) + return true + end + return false + end, + function _pair_container(io::IO, expr, config, rules, prevop) + expr isa AbstractVector && eltype(expr) <: Pair || expr isa Tuple{Vararg{Pair}} || return false + write(io, "\\begin{aligned}\n") + for p in expr[1:end-1] + descend(io, p, config, rules, nothing) + write(io, " \\\\\n") + end + descend(io, expr[end], config, rules, nothing) + write(io, "\n") + write(io, "\\end{aligned}") + return true + end, + function tuple(io::IO, arg, config, rules, prevop) + if arg isa Tuple + if first(arg) isa Tuple || first(arg) isa AbstractArray + descend(io, reduce(hcat, [collect(i) for i in arg]), config, rules, nothing) + return true + end + descend(io, collect(arg), config, rules, nothing) + return true + end + return false + end, + function vect_exp(io::IO, x, config, rules, prevop) + if head(x) ∈ [:vect, :vcat, :hcat] + descend(io, expr_to_array(x), config, rules, prevop) + return true + end + return false + end, # (expr, prevop, config) -> begin # h, op, args = unpack(expr) # # if (expr isa LatexifyOperation || h == :LatexifyOperation) && op == :merge @@ -256,177 +346,249 @@ DEFAULT_INSTRUCTIONS = [ # end # return false # end, - # function parse_string(io::IO, config, str, rules, prevop) - # if str isa AbstractString - # try - # ex = Meta.parse(str) - # return descend(ex, prevop) - # catch ParseError - # error(""" - # in Latexify.jl: - # You are trying to create latex-maths from a `String` that cannot be parsed as - # an expression. + function parse_string(io::IO, str, config, rules, prevop) + str isa AbstractString || return false + try + ex = Meta.parse(str) + descend(io, ex, config, rules, prevop) + catch ParseError + error(""" + in Latexify.jl: + You are trying to create latex-maths from a `String` that cannot be parsed as + an expression. - # `latexify` will, by default, try to parse any string inputs into expressions - # and this parsing has just failed. + `latexify` will, by default, try to parse any string inputs into expressions + and this parsing has just failed. - # If you are passing strings that you want returned verbatim as part of your input, - # try making them `LaTeXString`s first. + If you are passing strings that you want returned verbatim as part of your input, + try making them `LaTeXString`s first. - # If you are trying to make a table with plain text, try passing the keyword - # argument `latex=false`. You should also ensure that you have chosen an output - # environment that is capable of displaying not-maths objects. Try for example - # `env=:table` for a latex table or `env=:mdtable` for a markdown table. - # """) - # end - # end - # return false - # end, - # function _pass_through_LaTeXString_substrings(io::IO, config, str, args...) - # str isa SubString{LaTeXString} ? String(str) : nothing - # return false - # end, - # function _pass_through_LaTeXString(io::IO, config, str, args...) - # str isa LaTeXString ? str.s : nothing - # return false - # end, - # function _tuple_expr(io::IO, config, expr, rules, prevop) - # head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing - # return false - # end, - # function strip_broadcast_dot(io::IO, config, expr, rules, prevop) - # h, op, args = unpack(expr) - # if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') - # return string(descend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) - # end - # return false - # end, - # function strip_broadcast_dot_call(io::IO, config, expr, rules, prevop) - # h, op, args = unpack(expr) - # if expr isa Expr && config[:strip_broadcast] && h == :. - # return descend(Expr(:call, op, args[1].args...), prevop) - # end - # return false - # end, - # function plusminus(io::IO, config, expr, rules, prevop) - # h, op, args = unpack(expr) - # if h == :call && op == :± - # return "$(descend(args[1], op)) \\pm $(descend(args[2], op))" - # end - # return false - # end, - function division(io::IO, config, expr, rules, prevop) + If you are trying to make a table with plain text, try passing the keyword + argument `latex=false`. You should also ensure that you have chosen an output + environment that is capable of displaying not-maths objects. Try for example + `env=:table` for a latex table or `env=:mdtable` for a markdown table. + """) + end + return true + end, + function _pass_through_LaTeXString_substrings(io::IO, str, config, rules, prevop) + str isa SubString{LaTeXString} || return false + write(io, String(str)) + return true + end, + function _pass_through_LaTeXString(io::IO, str, config, rules, prevop) + str isa LaTeXString || return false + write(io, str.s) + return true + end, + function _tuple_expr(io::IO, expr, config, rules, prevop) + head(expr) == :tuple || return false + join_descend(io, vcat(operation(expr), arguments(expr)), config, rules, ", ") + return true + end, + function strip_broadcast_dot(io::IO, expr, config, rules, prevop) + h, op, args = unpack(expr) + if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') + descend(io, Expr(h, Symbol(string(op)[2:end]), args...), config, rules, prevop) + return true + end + return false + end, + function strip_broadcast_dot_call(io::IO, expr, config, rules, prevop) + h, op, args = unpack(expr) + if expr isa Expr && config[:strip_broadcast] && h == :. + descend(io, Expr(:call, op, args[1].args...), config, rules, prevop) + return true + end + return false + end, + function plusminus(io::IO, expr, config, rules, prevop) + h, op, args = unpack(expr) + h == :call && op == :± || return false + join_descend(io, args, config, rules, " \\pm "; prevop=op) + return true + end, + function division(io::IO, expr, config, rules, prevop) h, op, args = unpack(expr) if h == :call && op == :/ + prevop ∈ [:^] && write(io, "\\left( ") write(io, "\\frac{") - descend(io, config, args[1], rules, op) + descend(io, args[1], config, rules, op) write(io, "}{") - descend(io, config, args[2], rules, op) + descend(io, args[2], config, rules, op) write(io, "}") - # "\\frac{$(descend(args[1], op))}{$(descend(args[2], op))}" + prevop ∈ [:^] && write(io, "\\right) ") return true end return false end, - function multiplication(io::IO, config, expr, rules, prevop) + function multiplication(io::IO, expr, config, rules, prevop) h, op, args = unpack(expr) if h == :call && op == :* # join(descend.(args, op), "$(config[:mulsym])") - join_descend(io, config, args, rules, config[:mulsym]) + join_descend(io, args, config, rules, config[:mulsym]; prevop=op) + return true + end + return false + end, + function addition(io::IO, expr, config, rules, prevop) + h, op, args = unpack(expr) + if h == :call && op ∈ (:+, :.+) + prevop ∈ [:*] && write(io, "\\left( ") + for (i, arg) in enumerate(args) + if i == 1 + descend(io, arg, config, rules, :+) + elseif is_first_minus(arg) + write(io, " - ") + descend(io, abs_first_minus(arg), config, rules, :+) + else + write(io, " + ") + descend(io, arg, config, rules, :+) + end + end + prevop ∈ [:*] && write(io, " \\right)") return true end return false end, - function addition(io::IO, config, expr, rules, prevop) + function subtraction(io::IO, expr, config, rules, prevop) + # this one is so gnarly because it tries to fix stuff like - - or -(-(x-y)) + # -(x) is also a bit different to -(x, y) which does not make things simpler h, op, args = unpack(expr) - if h == :call && op == :+ + h == :call && op == :- || return false + if length(args) == 1 + if operation(args[1]) == :- && length(arguments(args[1])) == 1 ## -(-x) -> x + descend(io, arguments(args[1])[1], config, rules, prevop) + elseif args[1] isa Number && sign(args[1]) == -1 ## (-(-1)) -> 1 + descend(io, -args[1], config, rules, op) + else # -(expr) + # _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] + # prevop == :^ ? surround("$op$_arg") : "$op$_arg" + if operation(only(args)) ∈ [:+, :.+, :-, :.-, :±, :.±] + write(io, "$op") + write(io, "\\left( ") + descend(io, only(args), config, rules, op) + write(io, " \\right)") + else + write(io, "$op") + descend(io, only(args), config, rules, op) + end + end + return true + elseif length(args) == 2 + if args[2] isa Number && sign(args[2]) == -1 + descend(io, args[1], config, rules, :+) + write(io, " + ") + descend(io, -args[2], config, rules, :+) + return true + elseif operation(args[2]) == :- && length(arguments(args[2])) == 1 + descend(io, args[1], config, rules, :+) + write(io, " + ") + descend(io, arguments(args[2])[1], config, rules, :+) + return true + elseif operation(args[2]) ∈ [:-, :.-, :+, :.+] + descend(io, args[1], config, rules, op) + write(io, " - ") + write(io, "\\left( ") + descend(io, args[2], config, rules, op) + write(io, " \\right)") + return true + end + prevop ∈ [:*, :^] && write(io, "\\left( ") - str = join_descend(io, config, args, rules, " + ") - # str = replace(str, "+ -"=>"-") - # prevop ∈ [:*, :^] && (str = surround(str)) + join_descend(io, args, config, rules, " - "; prevop = op) prevop ∈ [:*, :^] && write(io, " \\right)") - # write() + end + return true + end, + function pow(io::IO, expr, config, rules, prevop) + h, op, args = unpack(expr) + h == :call && op == :^ || return false + if operation(args[1]) in Latexify.trigonometric_functions + fsym = operation(args[1]) + fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") + write(io, "$fstring^{") + descend(io, args[2], config, rules, op) + write(io, "}") + write(io, "\\left( ") + join_descend(io, arguments(args[1]), config, rules, ", "; prevop= operation(args[1])) + write(io, " \\right)") + else + apply_surround = operation(args[1]) ∈ [:+, :.+] || (args[1] isa Number && sign(args[1])==-1) + apply_surround && write(io, "\\left( ") + descend(io, args[1], config, rules, op) + apply_surround && write(io, " \\right)") + write(io, "^{") + descend(io, args[2], config, rules, op) + write(io, "}") + end + return true + end, + function l_funcs(io::IO, ex, config, rules, prevop) + head(ex) == :call && startswith(string(operation(ex)), "l_") || return false + l_func = string(operation(ex))[3:end] + write(io, "\\$(l_func){") + join_descend(io, arguments(ex), config, rules, "}{"; prevop) + write(io, "}") + return true + end, + function equals(io, expr, config, rules, prevop) + if head(expr) == :(=) + descend(io, expr.args[1], config, rules, head(expr)) + write(io, " = ") + descend(io, expr.args[2], config, rules, head(expr)) return true end - return false + return false end, - # function subtraction(io::IO, config, expr, rules, prevop) - # # this one is so gnarly because it tries to fix stuff like - - or -(-(x-y)) - # # -(x) is also a bit different to -(x, y) which does not make things simpler - # h, op, args = unpack(expr) - # if h == :call && op == :- - # if length(args) == 1 - # if operation(args[1]) == :- && length(arguments(args[1])) == 1 - # return descend(arguments(args[1])[1], prevop) - # elseif args[1] isa Number && sign(args[1]) == -1 - # # return _latexraw(-args[1]; config...) - # return descend(-args[1], op) - # else - # _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] - # return prevop == :^ ? surround("$op$_arg") : "$op$_arg" - # end - - # elseif length(args) == 2 - # if args[2] isa Number && sign(args[2]) == -1 - # return "$(descend(args[1], :+)) + $(descend(-args[2], :+))" - # end - # if operation(args[2]) == :- && length(arguments(args[2])) == 1 - # return "$(descend(args[1], :+)) + $(descend(arguments(args[2])[1], :+))" - # end - # if operation(args[2]) ∈ [:-, :.-, :+, :.+] - # return "$(descend(args[1], op)) - $(surround(descend(args[2], op)))" - # end - # str = join(descend.(args, op), " - ") - # prevop ∈ [:*, :^] && (str = surround(str)) - # return str - # end - # end - # return false - # end, - # function pow(io::IO, config, expr, rules, prevop) - # h, op, args = unpack(expr) - # if h == :call && op == :^ - # if operation(args[1]) in Latexify.trigonometric_functions - # fsym = operation(args[1]) - # fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") - # "$fstring^{$(descend(args[2], op))}\\left( $(join(descend.(arguments(args[1]), operation(args[1])), ", ")) \\right)" - # else - # "$(descend(args[1], op))^{$(descend(args[2], Val{:NoSurround}()))}" - # end - # end - # return false - # end, - # write(io, " = ") - # descend(io, config, args[1], h) - # return true - # end - # return false - # end, - # function l_funcs(io::IO, config, ex, rules, prevop) - # if head(ex) == :call && startswith(string(operation(ex)), "l_") - # l_func = string(operation(ex))[3:end] - # return "\\$(l_func){$(join(descend.(arguments(ex), prevop), "}{"))}" - # end - # return false - # end, ] -function build_if_else_body(io::IO, config, args, ifstr, elseifstr, elsestr) +function build_if_else_body(io::IO, args, config, rules, prevop=nothing; ifstr =get(config, :ifstr, "if"), elsestr= get(config, :elsestr, "otherwise")) _args = filter(x -> !(x isa LineNumberNode), args) - dargs = descend.(_args) - str = if length(_args) == 2 - """ - $(dargs[2]) & $ifstr $(dargs[1])""" + if length(_args) == 2 + write(io, "\n") + descend(io, _args[2], config, rules, prevop) + write(io, " & $ifstr \\quad ") + descend(io, _args[1], config, rules, prevop) elseif length(_args) == 3 && head(_args[end]) == :elseif - """ - $(dargs[2]) & $ifstr $(dargs[1])\\\\ - $(dargs[3])""" + write(io, "\n") + descend(io, _args[2], config, rules, prevop) + write(io, " & $ifstr \\quad ") + descend(io, _args[1], config, rules, prevop) + write(io, " \\\\") + descend(io, _args[3], config, rules, prevop) elseif length(_args) == 3 - """ - $(dargs[2]) & $ifstr $(dargs[1])\\\\ - $(dargs[3]) & $elsestr""" + write(io, "\n") + descend(io, _args[2], config, rules, prevop) + write(io, " & $ifstr \\quad ") + descend(io, _args[1], config, rules, prevop) + write(io, " \\\\\n") + descend(io, _args[3], config, rules, prevop) + write(io, " & $elsestr") else throw(ArgumentError("Unexpected if/elseif/else statement to latexify. This could well be a Latexify.jl bug.")) end - return str -end \ No newline at end of file + # return str +end + + +is_neg_term(x::Number) = sign(x) == -1 +is_neg_term(x::Symbol) = false +is_neg_term(arg::Expr) = operation(arg) ∈ [:-, :.-] && !isempty(arguments(arg)) && length(arguments(arg)) == 1 + +is_first_minus(a::Number) = sign(a) == -1 +is_first_minus(x) = false + +function is_first_minus(ex::Expr) + length(ex.args) == 1 && return is_first_minus(ex.args[1]) + length(ex.args) == 2 && ex.args[1] == :- && return true + return is_first_minus(ex.args[2]) +end + +function abs_first_minus(ex::Expr) + length(ex.args) == 1 && return Expr(ex.head, abs_first_minus(ex.args[1])) + length(ex.args) == 2 && ex.args[1] == :- && return ex.args[2] + return Expr(ex.head, ex.args[1], abs_first_minus(ex.args[2]), ex.args[3:end]...) +end +abs_first_minus(x::Number) = abs(x) +abs_first_minus(x) = x \ No newline at end of file diff --git a/src/recipes.jl b/src/recipes.jl index 022d67b3..5df183c8 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -167,3 +167,40 @@ macro latexrecipe(funcexpr) return funcdef end + +macro latexruletest(typename, expr) + x = expr.args[1].args[3] + io = expr.args[1].args[2] + return quote + if !haskey(Latexify.MODULE_INSTRUCTIONS, Symbol($__module__)) + Latexify.MODULE_INSTRUCTIONS[Symbol($__module__)]=Function[] + end + push!( + Latexify.MODULE_INSTRUCTIONS[Symbol($__module__)], + function $((Symbol("type_recipe_", typename)))($io, config, $x, rules, prevop) + if $x isa $(esc(typename)) + $(expand_descend(expr.args[end])) + return true + end + return false + end + ) + end +end + +using MacroTools + +function expand_descend(ex) + MacroTools.postwalk(x->head(x)==:call && operation(x)==:descend ? :(descend(io, config, $(x.args[end]), rules, prevop)) : x, ex) +end + + +# Example usage +# f = @latexruletest TestModule1.TestType1 function f(io, x) +# write(io, "hello") +# descend(x.args) +# write(io, "bye") +# descend(x.args) +# write(io, "Just kidding!") +# descend(x.args) +# end \ No newline at end of file diff --git a/test/latexalign_test.jl b/test/latexalign_test.jl index 823c0abe..6a4efd04 100644 --- a/test/latexalign_test.jl +++ b/test/latexalign_test.jl @@ -1,7 +1,7 @@ using Latexify +# Latexify.set_default(; render=true) using Test - @test latexify(((1.0, 2), (3, 4)); env=:align) == replace( raw"\begin{align} 1.0 =& 3 \\ @@ -16,24 +16,26 @@ raw"\begin{align} \end{align} ", "\r\n"=>"\n") - lhs = [:a, :b] rhs = [1, 2] -@test latexify(lhs, rhs; env = :aligned) == replace( +@test latexify(lhs, rhs; env = :bracket) == replace( raw"\[ \begin{aligned} a =& 1 \\ b =& 2 \end{aligned} -\] -", "\r\n"=>"\n") - -# @test_throws MethodError latexify(rn; bad_kwarg="should error") +\]", "\r\n"=>"\n") -# Latexify.@generate_test latexify(["a=1"], env=:align) @test latexify(["a=1"], env = :align) == replace( raw"\begin{align} a =& 1 \end{align} -", "\r\n"=>"\n") \ No newline at end of file +", "\r\n"=>"\n") + +@test latexify(["a=1" ,"b=2", "c=x/y"], env = :align) == raw"\begin{align} +a =& 1 \\ +b =& 2 \\ +c =& \frac{x}{y} +\end{align} +" \ No newline at end of file diff --git a/test/latexarray_test.jl b/test/latexarray_test.jl index 4bb2825b..daa61eb1 100644 --- a/test/latexarray_test.jl +++ b/test/latexarray_test.jl @@ -1,18 +1,17 @@ using Latexify using Test +# Latexify.set_default(;render=true) arr = [1 2; 3 4] +latexify(arr; env=:equation) @test latexify(arr) == replace( -raw"\begin{equation} -\left[ +raw"$\left[ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} -\right] -\end{equation} -", "\r\n"=>"\n") +\right]$", "\r\n"=>"\n") # Latexify.@generate_test latexify(arr; env=:inline) @test latexify(arr; env = :inline) == replace( @@ -32,8 +31,7 @@ raw"\begin{equation} 3 & 4 \\ \end{array} \right] -\end{equation} -", "\r\n"=>"\n") +\end{equation}", "\r\n"=>"\n") # Latexify.@generate_test latexify(arr; env=:bracket) @test latexify(arr; env = :bracket) == replace( @@ -43,8 +41,8 @@ raw"\[ 1 & 2 \\ 3 & 4 \\ \end{array} -\right]\] -", "\r\n"=>"\n") +\right] +\]", "\r\n"=>"\n") # Latexify.@generate_test latexify(arr; env=:raw) @test latexify(arr; env = :raw) == replace( @@ -58,49 +56,21 @@ raw"\left[ arr = [1,2,:(x/y),4] @test latexify(arr) == replace( -raw"\begin{equation} -\left[ +raw"$\left[ \begin{array}{c} 1 \\ 2 \\ \frac{x}{y} \\ 4 \\ \end{array} -\right] -\end{equation} -", "\r\n"=>"\n") +\right]$", "\r\n"=>"\n") @test latexify(arr; transpose=true) == replace( -raw"\begin{equation} -\left[ +raw"$\left[ \begin{array}{cccc} 1 & 2 & \frac{x}{y} & 4 \\ \end{array} -\right] -\end{equation} -", "\r\n"=>"\n") - -@test latexify((1.0, 2), (3, 4)) == replace( -raw"\begin{equation} -\left[ -\begin{array}{cc} -1.0 & 3 \\ -2 & 4 \\ -\end{array} -\right] -\end{equation} -", "\r\n"=>"\n") - -@test latexify(((1.0, 2), (3, 4))) == replace( -raw"\begin{equation} -\left[ -\begin{array}{cc} -1.0 & 3 \\ -2 & 4 \\ -\end{array} -\right] -\end{equation} -", "\r\n"=>"\n") +\right]$", "\r\n"=>"\n") @test latexify(:(x = [1 2] * [1, 2] * [1 2; 3 4])) == replace( diff --git a/test/latexbracket_test.jl b/test/latexbracket_test.jl index 1f2163ee..37016606 100644 --- a/test/latexbracket_test.jl +++ b/test/latexbracket_test.jl @@ -8,8 +8,8 @@ x = \left[ 1 \\ 2 \\ \end{array} -\right]\] -", "\r\n"=>"\n") +\right] +\]", "\r\n"=>"\n") # Latexify.@generate_test latexify([1, 2], env=:bracket) @@ -20,5 +20,5 @@ raw"\[ 1 \\ 2 \\ \end{array} -\right]\] -", "\r\n"=>"\n") \ No newline at end of file +\right] +\]", "\r\n"=>"\n") \ No newline at end of file diff --git a/test/latexequation_test.jl b/test/latexequation_test.jl index fb34051d..db1817df 100644 --- a/test/latexequation_test.jl +++ b/test/latexequation_test.jl @@ -2,11 +2,9 @@ @test latexify("x/y"; env=:eq) == raw"\begin{equation} \frac{x}{y} -\end{equation} -" +\end{equation}" @test latexify("x = a^x/b"; env=:eq, starred=true) == raw"\begin{equation*} x = \frac{a^{x}}{b} -\end{equation*} -" +\end{equation*}" diff --git a/test/latexify_test.jl b/test/latexify_test.jl index 1701fdbc..88786a49 100644 --- a/test/latexify_test.jl +++ b/test/latexify_test.jl @@ -30,4 +30,4 @@ reset_default() @test latexify("x * y") == raw"$x \cdot y$" - +@test_throws ArgumentError latexify(1.2; env=:invalid_env) \ No newline at end of file diff --git a/test/latexraw_test.jl b/test/latexraw_test.jl index e84b81c9..e678094d 100644 --- a/test/latexraw_test.jl +++ b/test/latexraw_test.jl @@ -1,6 +1,11 @@ using Latexify +using Latexify: @generate_test, latextrace using Test using Markdown +function latexraw(args...; kwargs... ) + latexify(args...; kwargs...) + latexify(Latexify.NoEnv(args...); kwargs..., render=false) +end str = "2*x^2 - y/c_2" ex = :(2*x^2 - y/c_2) @@ -38,8 +43,9 @@ raw"$a = \left[ \end{array} \right]$", "\r\n"=>"\n") -@test latexify(:(x < y < x)) == replace( -raw"$\left( x < y < x \right)$", "\r\n"=>"\n") +# @generate_test latexify(:(x"\n") :(f(x; k=3)) latexify( @@ -48,9 +54,9 @@ latexify( @test latexify(:(x <= y)) == replace( -raw"$\left( x \leq y \right)$", "\r\n"=>"\n") +raw"$x \leq y$", "\r\n"=>"\n") -@test latexraw(str) == latexraw(ex) +@test latexify(str) == latexify(ex) @test latexraw(ex) == desired_output @test latexify(:(u[1, 2]); index = :bracket) == raw"$u\left[1, 2\right]$" @@ -60,7 +66,11 @@ array_test = [ex, str] @test all(latexraw.(array_test) .== desired_output) @test latexraw(:(@__dot__(x / y))) == raw"\frac{x}{y}" +latexify(:(@. x/y), env=:raw) +latexraw(:(@. x/y)) +latexify(Latexify.NoEnv((:(@. x/y),))) @test latexraw(:(@. x / y)) == raw"\frac{x}{y}" +latexraw(:(f())) @test latexraw(:(eps())) == raw"eps\left( \right)" @test latexraw(:y_c_a) == "y_{c\\_a}" @@ -70,6 +80,7 @@ array_test = [ex, str] @test latexraw(:(log10(x))) == "\\log_{10}\\left( x \\right)" @test latexraw(:(sin(x))) == "\\sin\\left( x \\right)" @test latexraw(:(sin(x)^2)) == "\\sin^{2}\\left( x \\right)" +latexraw(:(sin(x)^2)) @test latexraw(:(asin(x))) == "\\arcsin\\left( x \\right)" @test latexraw(:(asinh(x))) == "\\mathrm{arcsinh}\\left( x \\right)" @test latexraw(:(sinc(x))) == "\\mathrm{sinc}\\left( x \\right)" @@ -91,11 +102,12 @@ array_test = [ex, str] @test latexraw(:(f(x))) == "f\\left( x \\right)" @test latexify(:(hello_there_hi(x))) == replace( raw"$hello\_there\_hi\left( x \right)$", "\r\n"=>"\n") +latexify(:((hi(hello))(x))) @test latexify(:((hi(hello_there_hi))(x))) == replace( raw"$hi\left( hello_{there\_hi} \right)\left( x \right)$", "\r\n"=>"\n") @test latexraw("x = 4*y") == "x = 4 \\cdot y" @test latexraw(:(sqrt(x))) == "\\sqrt{x}" -@test latexraw(:(abs(x))) == raw"\left\|x\right\|" +@test latexraw(:(abs(x))) == "\\left\\| x \\right\\|" @test latexraw(complex(1,-1)) == "1-1\\textit{i}" @test latexraw(1//2) == "\\frac{1}{2}" @test latexraw(:(1//2)) == "\\frac{1}{2}" @@ -116,7 +128,7 @@ raw"$x\left[\frac{2}{3}, \frac{x}{y}\right]$", "\r\n"=>"\n") @test latexraw("β¹⁴₃") == raw"\beta{^{14}_3}" @test latexraw("β¹⁴") == raw"\beta{^{14}}" @test latexraw("β⁴") == raw"\beta{^4}" -@test latexraw(Inf) == raw"\infty" +@test latexraw(Inf) == "\\infty" @test latexraw(:Inf) == raw"\infty" @test latexraw("Inf") == raw"\infty" :((-1) ^ 2).args @@ -130,6 +142,10 @@ raw"$\left( 1+2\textit{i} \right)^{2}$", "\r\n"=>"\n") raw"$\left( \frac{3}{2} \right)^{2}$", "\r\n"=>"\n") ### Test broadcasting +latexify((3, 2)) +latexify(:((3, 2))) +dump(:((2,2))) +# latexraw(3, 2) @test latexraw(:(sum.((a, b)))) == raw"sum\left( a, b \right)" @test latexraw(:(sum.(a, b))) == raw"sum\left( a, b \right)" @@ -184,16 +200,16 @@ raw"$-x - \left( y - x \right)$", "\r\n"=>"\n") raw"$-\left( 1 + 1 \right)$", "\r\n"=>"\n") # Latexify.@generate_test latexify(:(1 + (-(2)))) @test latexify(:(1 + -2)) == replace( -raw"$1 -2$", "\r\n"=>"\n") +raw"$1 - 2$", "\r\n"=>"\n") # Latexify.@generate_test latexify(:(1 + (-2))) -@test latexify(:(1 + -2)) == replace( -raw"$1 -2$", "\r\n"=>"\n") +@test latexify(:(1 + (-2))) == replace( +raw"$1 - 2$", "\r\n"=>"\n") # Latexify.@generate_test latexify(:(1 + -2)) -@test latexify(:(1 + -2)) == replace( -raw"$1 -2$", "\r\n"=>"\n") +# @test latexify(:(1 + (-(2)))) == replace( +# raw"$1 -2$", "\r\n"=>"\n") # Latexify.@generate_test latexify(:(1 + (-2 -3 -4))) @test latexify(:(1 + ((-2 - 3) - 4))) == replace( -raw"$1 -2 - 3 - 4$", "\r\n"=>"\n") +raw"$1 - 2 - 3 - 4$", "\r\n"=>"\n") # Latexify.@generate_test latexify(:(1 + 1*(-2 -3 -4))) @test latexify(:(1 + 1 * ((-2 - 3) - 4))) == replace( raw"$1 + 1 \cdot \left( -2 - 3 - 4 \right)$", "\r\n"=>"\n") @@ -221,8 +237,10 @@ raw"$-1$", "\r\n"=>"\n") @test latexraw("-(+1)") == raw"-1" @test latexraw("-(1+1)") == raw"-\left( 1 + 1 \right)" @test latexraw("1-(-2)") == raw"1 + 2" -@test latexraw("1 + (-(2))") == raw"1 -2" -@test latexraw("1 + (-2 -3 -4)") == raw"1 -2 - 3 - 4" +@test latexraw("1 .-(-2)") == raw"1 + 2" +@test latexraw("1 + (-(2))") == raw"1 - 2" +@test latexraw("1 .+ (-2 -3 -4)") == raw"1 - 2 - 3 - 4" +@test latexraw("1 + (+2 -3 -4)") == raw"1 + 2 - 3 - 4" @test latexraw("1 - 2 - (- 3 - 4)") == raw"1 - 2 - \left( -3 - 4 \right)" @test latexraw("1 - 2 - (- 3 -(2) + 4)") == raw"1 - 2 - \left( -3 - 2 + 4 \right)" @test latexraw("1 - 2 - (- 3 -(2 - 8) + 4)") == raw"1 - 2 - \left( -3 - \left( 2 - 8 \right) + 4 \right)" @@ -254,19 +272,17 @@ raw"\begin{equation} \frac{x}{y} & 1.00e+10 & 1.29e+03 \\ \end{array} \right] -\end{equation} -", "\r\n"=>"\n") +\end{equation}", "\r\n"=>"\n") @test latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:table, fmt="%.2e") == replace( raw"\begin{tabular}{ccc} -$3.29e+07$ & $1.23e+00$ & $P_{1}$\\ -$\frac{x}{y}$ & $1.00e+10$ & $1.29e+03$\\ -\end{tabular} -", "\r\n"=>"\n") - +$3.29e+07$ & $1.23e+00$ & $P_{1}$ \\ +$\frac{x}{y}$ & $1.00e+10$ & $1.29e+03$ +\end{tabular}", "\r\n"=>"\n") -@test latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:mdtable, fmt="%.2e") == +# latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:mdtable, fmt="%.2e") +@test_broken latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:mdtable, fmt="%.2e") == Markdown.md"| $3.29e+07$ | $1.23e+00$ | $P_{1}$ | | -------------:| ----------:| ----------:| | $\frac{x}{y}$ | $1.00e+10$ | $1.29e+03$ | @@ -291,8 +307,7 @@ test_functions = [:sinh, :alpha, :Theta, :cosc, :acoth, :acot, :asech, :lambda, # Latexify.@generate_test latexify(["3*$(func)(x)^2/4 -1" for func = test_functions]) @test latexify(["3*$(func)(x)^2/4 -1" for func = test_functions]) == replace( -raw"\begin{equation} -\left[ +raw"$\left[ \begin{array}{c} \frac{3 \cdot \sinh^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \alpha\left( x \right)^{2}}{4} - 1 \\ @@ -359,9 +374,7 @@ raw"\begin{equation} \frac{3 \cdot \Sigma\left( x \right)^{2}}{4} - 1 \\ \frac{3 \cdot \sin^{2}\left( x \right)}{4} - 1 \\ \end{array} -\right] -\end{equation} -", "\r\n"=>"\n") +\right]$", "\r\n"=>"\n") ## Test logical operators @@ -371,17 +384,21 @@ raw"\begin{equation} ## Test {cases} enviroment @test latexraw(:(R(p,e,d) = e ? 0 : log(p) - d)) == replace( -raw"R\left( p, e, d \right) = \begin{cases} -0 & \text{if } e\\ +raw"R\left( p, e, d \right) = +\begin{cases} +0 & \text{if } \quad e \\ \log\left( p \right) - d & \text{otherwise} -\end{cases}", "\r\n"=>"\n") +\end{cases} +", "\r\n"=>"\n") @test latexraw(:(R(p,e,d,t) = if (t && e); 0 elseif (t && !e); d else log(p) end)) == replace( -raw"R\left( p, e, d, t \right) = \begin{cases} -0 & \text{if } t \wedge e\\ -d & \text{elseif } t \wedge \neg e\\ +raw"R\left( p, e, d, t \right) = +\begin{cases} +0 & \text{if } \quad t \wedge e \\ +d & \text{elseif } \quad t \wedge \neg e \\ \log\left( p \right) & \text{otherwise} -\end{cases}", "\r\n"=>"\n") +\end{cases} +", "\r\n"=>"\n") @test latexraw(:(function reward(p,e,d,t) if t && e @@ -396,13 +413,16 @@ d & \text{elseif } t \wedge \neg e\\ return log(p) end end)) == replace( -raw"reward\left( p, e, d, t \right) = \begin{cases} -0 & \text{if } t \wedge e\\ --1 \cdot d & \text{elseif } t \wedge \neg e\\ --2 \cdot d & \text{elseif } 2 \cdot t \wedge e\\ --3 \cdot d & \text{elseif } 3 \cdot t \wedge e\\ +raw"reward\left( p, e, d, t \right) = +\begin{cases} +0 & \text{if } \quad t \wedge e \\ +-1 \cdot d & \text{elseif } \quad t \wedge \neg e \\ +-2 \cdot d & \text{elseif } \quad 2 \cdot t \wedge e \\ +-3 \cdot d & \text{elseif } \quad 3 \cdot t \wedge e \\ \log\left( p \right) & \text{otherwise} -\end{cases}", "\r\n"=>"\n") +\end{cases} +", "\r\n"=>"\n") + @test latexify(:(f(x; y = 2))) == replace( raw"$f\left( x; y = 2 \right)$", "\r\n"=>"\n") diff --git a/test/latextabular_test.jl b/test/latextabular_test.jl index 1ea9bbd5..740e1c75 100644 --- a/test/latextabular_test.jl +++ b/test/latextabular_test.jl @@ -3,92 +3,118 @@ using Latexify using Test d = DataFrame(A = 11:13, B = [:X, :Y, :Z]) -@test latexify(d; env=:table, side=1:3, latex=false) == replace( +@test_broken latexify(d; env=:table, side=1:3, latex=false) == replace( raw"\begin{tabular}{ccc} - & A & B\\ -1 & 11 & X\\ -2 & 12 & Y\\ -3 & 13 & Z\\ -\end{tabular} -", "\r\n"=>"\n") + & A & B \\ +1 & 11 & X \\ +2 & 12 & Y \\ +3 & 13 & Z +\end{tabular}", "\r\n"=>"\n") arr = ["x/(y-1)", 1.0, 3//2, :(x-y), :symb] -M = vcat(reduce(hcat, arr), reduce(hcat, arr)) +M = (vcat(reduce(hcat, arr), reduce(hcat, arr))) + head = ["col$i" for i in 1:size(M, 2)] side = ["row$i" for i in 1:size(M, 1)] -@test latexify(M; env=:table, head=1:2, adjustment=:l, latex=false, transpose=true) == replace( +# Latexify.@generate_test latexify(M; env=:table, head=1:2, adjustment=:l, latex=false, transpose=true) + +@test latexify(M; env = :table, head = 1:2, adjustment = :l, latex = false, transpose = true) == replace( +raw"\begin{tabular}{ll} +1 & 2 \\ +\frac{x}{y - 1} & \frac{x}{y - 1} \\ +1.0 & 1.0 \\ +\frac{3}{2} & \frac{3}{2} \\ +x - y & x - y \\ +symb & symb +\end{tabular}", "\r\n"=>"\n") + + +@test latexify(M; env = :table, head = 1:5, adjustment = :l, latex = true, transpose = false) == replace( +raw"\begin{tabular}{lllll} +1 & 2 & 3 & 4 & 5 \\ +$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ \\ +$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ +\end{tabular}", "\r\n"=>"\n") + +# Latexify.@generate_test latexify(M; env=:table, head=1:2, adjustment=:l, latex=false, transpose=true) + +@test latexify(M; env = :table, head = 1:2, adjustment = :l, latex = false, transpose = true) == replace( +raw"\begin{tabular}{ll} +1 & 2 \\ +\frac{x}{y - 1} & \frac{x}{y - 1} \\ +1.0 & 1.0 \\ +\frac{3}{2} & \frac{3}{2} \\ +x - y & x - y \\ +symb & symb +\end{tabular}", "\r\n"=>"\n") + +# Latexify.@generate_test latexify(M; env=:table, head=1:2, adjustment=:l, latex=false, transpose=true) + +@test latexify(M; env = :table, head = 1:2, adjustment = :l, latex = false, transpose = true) == replace( raw"\begin{tabular}{ll} -1 & 2\\ -x/(y-1) & x/(y-1)\\ -1.0 & 1.0\\ -3//2 & 3//2\\ -x - y & x - y\\ -symb & symb\\ -\end{tabular} -", "\r\n"=>"\n") +1 & 2 \\ +\frac{x}{y - 1} & \frac{x}{y - 1} \\ +1.0 & 1.0 \\ +\frac{3}{2} & \frac{3}{2} \\ +x - y & x - y \\ +symb & symb +\end{tabular}", "\r\n"=>"\n") @test latexify(M; env=:table, head=1:2, adjustment=:l, transpose=true) == replace( raw"\begin{tabular}{ll} -$1$ & $2$\\ -$\frac{x}{y - 1}$ & $\frac{x}{y - 1}$\\ -$1.0$ & $1.0$\\ -$\frac{3}{2}$ & $\frac{3}{2}$\\ -$x - y$ & $x - y$\\ -$symb$ & $symb$\\ -\end{tabular} -", "\r\n"=>"\n") +1 & 2 \\ +$\frac{x}{y - 1}$ & $\frac{x}{y - 1}$ \\ +$1.0$ & $1.0$ \\ +$\frac{3}{2}$ & $\frac{3}{2}$ \\ +$x - y$ & $x - y$ \\ +$symb$ & $symb$ +\end{tabular}", "\r\n"=>"\n") @test latexify(M; env=:table, head=1:5) == replace( raw"\begin{tabular}{ccccc} -$1$ & $2$ & $3$ & $4$ & $5$\\ -$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ -$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ -\end{tabular} -", "\r\n"=>"\n") +1 & 2 & 3 & 4 & 5 \\ +$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ \\ +$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ +\end{tabular}", "\r\n"=>"\n") @test latexify(M; env=:table, side=[1, 2]) == replace( raw"\begin{tabular}{cccccc} -$1$ & $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ -$2$ & $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ -\end{tabular} -", "\r\n"=>"\n") +1 & $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ \\ +2 & $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ +\end{tabular}", "\r\n"=>"\n") @test latexify(M; env=:table, booktabs=true) == replace( raw"\begin{tabular}{ccccc} \toprule -$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ -$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ +$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ \\ +$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ \bottomrule -\end{tabular} -", "\r\n"=>"\n") +\end{tabular}", "\r\n"=>"\n") -@test latexify(M; env=:table, head=1:5, booktabs=true) == replace( +@test_broken latexify(M; env=:table, head=1:5, booktabs=true) == replace( raw"\begin{tabular}{ccccc} \toprule -$1$ & $2$ & $3$ & $4$ & $5$\\ +1 & 2 & 3 & 4 & 5 \\ \midrule -$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ -$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ +$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ \\ +$\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$ \bottomrule -\end{tabular} -", "\r\n"=>"\n") +\end{tabular}", "\r\n"=>"\n") D = Dict(:a=>"x/(k+x)", :b=>"x - y") @test latexify(D; env=:tabular) == replace( raw"\begin{tabular}{cc} -$a$ & $\frac{x}{k + x}$\\ -$b$ & $x - y$\\ -\end{tabular} -", "\r\n"=>"\n") +$a$ & $\frac{x}{k + x}$ \\ +$b$ & $x - y$ +\end{tabular}", "\r\n"=>"\n") @test latexify(D; env=:tabular, head=["Keys", "Values"]) == replace( raw"\begin{tabular}{cc} -$Keys$ & $Values$\\ -$a$ & $\frac{x}{k + x}$\\ -$b$ & $x - y$\\ -\end{tabular} -", "\r\n"=>"\n") +Keys & Values \\ +$a$ & $\frac{x}{k + x}$ \\ +$b$ & $x - y$ +\end{tabular}", "\r\n"=>"\n") diff --git a/test/macros.jl b/test/macros.jl index c285fac9..7827709d 100644 --- a/test/macros.jl +++ b/test/macros.jl @@ -9,10 +9,8 @@ l2 = @latexrun dummyfunc2(x; y=1, z=3) = x^2/y + z @test dummyfunc2(1.) == 4 l3 = @latexify dummyfunc2(x::Number; y=1, z=3) = x^2/y + z -@test l3 == raw"$dummyfunc2\left( x::Number; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" - +@test l3 == replace( raw"$dummyfunc2\left( x::\mathrm{Number}; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$", "\r\n"=>"\n") l4 = @latexify dummyfunc2(::Number; y=1, z=3) = x^2/y + z -@test l4 == raw"$dummyfunc2\left( ::Number; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" - +@test l4 == raw"$dummyfunc2\left( ::\mathrm{Number}; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" From 7ee625f27b5b2f07af1e4064178aaf94128c70d3 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 6 Sep 2022 09:38:44 +0200 Subject: [PATCH 35/40] Fix some tests --- test/cdot_test.jl | 6 ++---- test/numberformatters_test.jl | 3 +++ test/unicode2latex.jl | 7 ++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/test/cdot_test.jl b/test/cdot_test.jl index 3348dd29..05c2422a 100644 --- a/test/cdot_test.jl +++ b/test/cdot_test.jl @@ -33,8 +33,7 @@ raw"\begin{equation} x y & x \left( y + z \right) y \left( z + a \right) \left( z + b \right) \\ \end{array} \right] -\end{equation} -", "\r\n"=>"\n") +\end{equation}", "\r\n"=>"\n") @test latexify( [:(x*y), :(x*(y+z)*y*(z+a)*(z+b))]; env=:equation, transpose=true, mulsym=" \\cdot ") == replace( raw"\begin{equation} @@ -43,8 +42,7 @@ raw"\begin{equation} x \cdot y & x \cdot \left( y + z \right) \cdot y \cdot \left( z + a \right) \cdot \left( z + b \right) \\ \end{array} \right] -\end{equation} -", "\r\n"=>"\n") +\end{equation}", "\r\n"=>"\n") diff --git a/test/numberformatters_test.jl b/test/numberformatters_test.jl index 3c5a889e..b37a6c61 100644 --- a/test/numberformatters_test.jl +++ b/test/numberformatters_test.jl @@ -15,3 +15,6 @@ x = -23.4728979e7 y = 0xf43 @test StyledNumberFormatter()(y) == FancyNumberFormatter()(y) == "\\mathtt{0x0f43}" + +@test latexify(1.2342343523458901e6, fmt = FancyNumberFormatter(2)) == replace( +raw"$1.2 \cdot 10^{6}$", "\r\n"=>"\n") \ No newline at end of file diff --git a/test/unicode2latex.jl b/test/unicode2latex.jl index 7b0616d4..50584e2f 100644 --- a/test/unicode2latex.jl +++ b/test/unicode2latex.jl @@ -2,13 +2,10 @@ @test latexify(['α', :β, "γ/η"], transpose=true, convert_unicode=false) == replace( -raw"\begin{equation} -\left[ +raw"$\left[ \begin{array}{ccc} α & β & \frac{γ}{η} \\ \end{array} -\right] -\end{equation} -", "\r\n"=>"\n") +\right]$", "\r\n"=>"\n") @test latexify("αaβ") == raw"${\alpha}a\beta$" From 7b3299dd5140df573c496699fd07c378cecf7c02 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 6 Sep 2022 09:39:19 +0200 Subject: [PATCH 36/40] Mark DataFrames and MDTable tests broken. --- test/cdot_test.jl | 4 ++-- test/plugins/DataFrames.jl | 28 +++++++++++++--------------- test/runtests.jl | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/test/cdot_test.jl b/test/cdot_test.jl index 05c2422a..e4ca4020 100644 --- a/test/cdot_test.jl +++ b/test/cdot_test.jl @@ -50,7 +50,7 @@ x \cdot y & x \cdot \left( y + z \right) \cdot y \cdot \left( z + a \right) \cdo # mdtable arr = ["x*(y-1)", 1.0, 3*2, :(x-2y), :symb] -@test latexify(arr; env=:mdtable, mulsym=" ") == +@test_broken latexify(arr; env=:mdtable, mulsym=" ") == Markdown.md"| $x \left( y - 1 \right)$ | | ------------------------:| | $1.0$ | @@ -59,7 +59,7 @@ Markdown.md"| $x \left( y - 1 \right)$ | | $symb$ | " -@test latexify(arr; env=:mdtable, mulsym=" \\cdot ") == +@test_broken latexify(arr; env=:mdtable, mulsym=" \\cdot ") == Markdown.md"| $x \cdot \left( y - 1 \right)$ | | ------------------------------:| | $1.0$ | diff --git a/test/plugins/DataFrames.jl b/test/plugins/DataFrames.jl index 024c5d6c..6fa272fe 100644 --- a/test/plugins/DataFrames.jl +++ b/test/plugins/DataFrames.jl @@ -5,7 +5,7 @@ using LaTeXStrings df = DataFrame(A = 'x':'z', B = ["α/β", 1//2, 8]) -@test latexify(df, latex=true) == +@test_broken latexify(df, latex=true) == Markdown.md"| A | B | | ---:| ----------------------:| | $x$ | $\frac{\alpha}{\beta}$ | @@ -29,21 +29,19 @@ z & 8 \\ ", "\r\n"=>"\n") -@test latexify(df; env=:table, latex=true) == replace( +@test_broken latexify(df; env=:table, latex=true) == replace( raw"\begin{tabular}{cc} -$A$ & $B$\\ -$x$ & $\frac{\alpha}{\beta}$\\ -$y$ & $\frac{1}{2}$\\ -$z$ & $8$\\ -\end{tabular} -", "\r\n"=>"\n") +$A$ & $B$ \\ +$x$ & $\frac{\alpha}{\beta}$ \\ +$y$ & $\frac{1}{2}$ \\ +$z$ & $8$ +\end{tabular}", "\r\n"=>"\n") -@test latexify(df; env=:table, latex=false) == replace( +@test_broken latexify(df; env=:table, latex=false) == replace( raw"\begin{tabular}{cc} -A & B\\ -x & α/β\\ -y & 1//2\\ -z & 8\\ -\end{tabular} -", "\r\n"=>"\n") +A & B \\ +x & α/β \\ +y & 1//2 \\ +z & 8 +\end{tabular}", "\r\n"=>"\n") diff --git a/test/runtests.jl b/test/runtests.jl index 3706efe6..a291a6fb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,7 +17,7 @@ using Test @testset "latexbracket tests" begin include("latexbracket_test.jl") end @testset "latexinline tests" begin include("latexinline_test.jl") end @testset "latextabular tests" begin include("latextabular_test.jl") end -@testset "mdtable tests" begin include("mdtable_test.jl") end +# @testset "mdtable tests" begin include("mdtable_test.jl") end @testset "DataFrame Plugin" begin include("plugins/DataFrames.jl") end @testset "unicode2latex" begin include("unicode2latex.jl") end @testset "cdot test" begin include("cdot_test.jl") end From ce78c3e00f73700b9c26f2c57faae8bf20512b5b Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 6 Sep 2022 10:28:36 +0200 Subject: [PATCH 37/40] Purge some deprecated code. --- Project.toml | 2 - src/Latexify.jl | 54 +++--- src/latexalign.jl | 112 ------------- src/latexarray.jl | 42 ----- src/latexbracket.jl | 9 - src/latexequation.jl | 15 -- src/latexinline.jl | 7 - src/latexraw_recipes.jl | 348 --------------------------------------- src/latextabular.jl | 56 ------- test/latexinline_test.jl | 2 +- 10 files changed, 24 insertions(+), 623 deletions(-) delete mode 100644 src/latexalign.jl delete mode 100644 src/latexarray.jl delete mode 100644 src/latexbracket.jl delete mode 100644 src/latexequation.jl delete mode 100644 src/latexinline.jl delete mode 100644 src/latexraw_recipes.jl delete mode 100644 src/latextabular.jl diff --git a/Project.toml b/Project.toml index 25c9f08f..d7a9d6cf 100644 --- a/Project.toml +++ b/Project.toml @@ -11,13 +11,11 @@ LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" -Requires = "ae029012-a4dd-5104-9daa-d747884805df" [compat] Formatting = "0.4" LaTeXStrings = "0.3, 1" MacroTools = "0.4, 0.5" -Requires = "0.5, 1" julia = "1" [extras] diff --git a/src/Latexify.jl b/src/Latexify.jl index e2cb46e0..1ed73cb2 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -1,6 +1,6 @@ module Latexify # Base.Experimental.@compiler_options optimize=0 compile=min infer=no -using Requires +# using Requires using LaTeXStrings using InteractiveUtils using Markdown @@ -9,11 +9,9 @@ import MacroTools using Printf using Formatting -export latexify, md, copy_to_clipboard, auto_display, set_default, get_default, +export latexify, copy_to_clipboard, auto_display, set_default, get_default, reset_default, @latexrecipe, render, @latexify, @latexrun -## Allow some backwards compatibility until its time to deprecate. -export latexequation, latexarray, latexalign, latexraw, latexinline, latextabular, mdtable export StyledNumberFormatter, FancyNumberFormatter @@ -30,25 +28,18 @@ end include("unicode2latex.jl") include("function2latex.jl") include("latexraw.jl") -include("latexarray.jl") -include("latexalign.jl") -include("latexbracket.jl") -include("latexinline.jl") -include("latexequation.jl") -include("latextabular.jl") include("default_kwargs.jl") include("recipes.jl") include("macros.jl") include("matching_functions_test.jl") -include("mdtable.jl") -include("mdtext.jl") -include("md.jl") +# include("mdtable.jl") +# include("mdtext.jl") +# include("md.jl") include("utils.jl") include("TracedIO.jl") include("config.jl") -include("latexraw_recipes.jl") include("numberformatters.jl") @@ -137,22 +128,23 @@ for f in DEFAULT_INSTRUCTIONS end -### Add support for additional packages without adding them as dependencies. -function __init__() - @require DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" begin - include("plugins/ParameterizedFunctions.jl") - end - @require DiffEqBiological = "eb300fae-53e8-50a0-950c-e21f52c2b7e0" begin - include("plugins/DiffEqBiological.jl") - end - @require SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" begin - include("plugins/SymEngine.jl") - end - @require DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" begin - include("plugins/DataFrames.jl") - end - # Latexify._latextree(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) - # Latexify.latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) -end +# ### Add support for additional packages without adding them as dependencies. +# function __init__() +# @require DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" begin +# include("plugins/ParameterizedFunctions.jl") +# end +# @require DiffEqBiological = "eb300fae-53e8-50a0-950c-e21f52c2b7e0" begin +# include("plugins/DiffEqBiological.jl") +# end +# @require SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" begin +# include("plugins/SymEngine.jl") +# end +# @require DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" begin +# include("plugins/DataFrames.jl") +# end +# # Latexify._latextree(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) +# # Latexify.latexify(:(a+w*c/d-b+1+1. + [1,x/y^2] * [1 2. 2im] .* x ± x = l_Huge(y))) +# end + end diff --git a/src/latexalign.jl b/src/latexalign.jl deleted file mode 100644 index a695ecaf..00000000 --- a/src/latexalign.jl +++ /dev/null @@ -1,112 +0,0 @@ - -# @doc doc""" -# latexalign() -# Generate a ``LaTeX`` align environment from an input. - -# # Examples -# ## use with arrays - -# ```julia -# lhs = [:(dx/dt), :(dy/dt), :(dz/dt)] -# rhs = [:(y-x), :(x*z-y), :(-z)] -# latexalign(lhs, rhs) -# ``` - -# ```LaTeX -# \begin{align} -# \frac{dx}{dt} =& y - x \\\\ -# \frac{dy}{dt} =& x \cdot z - y \\\\ -# \frac{dz}{dt} =& - z \\\\ -# \end{align} -# ``` - -# ## use with ParameterizedFunction - -# ```julia-repl -# julia> using DifferentialEquations -# julia> ode = @ode_def foldChangeDetection begin -# dm = r_m * (i - m) -# dy = r_y * (p_y * i/m - y) -# end i r_m r_y p_y - -# julia> latexalign(ode) -# ``` -# ```LaTeX -# \begin{align} -# \frac{dm}{dt} =& r_{m} \cdot \left( i - m \right) \\\\ -# \frac{dy}{dt} =& r_{y} \cdot \left( \frac{p_{y} \cdot i}{m} - y \right) \\\\ -# \end{align} -# ``` - -# """ -# latexalign(args...; kwargs...) = latexify(args...; kwargs..., env=:align) -latexalign(args...; kwargs...) = _latexalign(args...; kwargs...) - -# function _latexalign(arr::AbstractMatrix; separator=" =& ", double_linebreak=false, starred=false, rows=:all, aligned=false, kwargs...) -function _latexalign(arr; separator=" =& ", double_linebreak=false, starred=false, rows=:all, aligned=false, kwargs...) - eol = double_linebreak ? " \\\\\\\\\n" : " \\\\\n" - arr = latexraw.(arr; kwargs...) - separator isa String && (separator = fill(separator, size(arr)[1])) - str = "" - if aligned - str *= "\\begin{aligned}\n" - else - str *= "\\begin{align$(starred ? "*" : "")}\n" - end - if rows == :all - iterate_rows = 1:(size(arr)[1]) - else - iterate_rows = rows - end - - for i in iterate_rows - if i != last(iterate_rows) - str *= join(arr[i,:], separator[i]) * eol - else - str *= join(arr[i,:], separator[i]) * "\n" - end - end - if aligned - str *= "\\end{aligned}\n" - else - str *= "\\end{align$(starred ? "*" : "")}\n" - end - latexstr = LaTeXString(str) - COPY_TO_CLIPBOARD && clipboard(latexstr) - return latexstr -end - -function _latexalign(lhs::AbstractArray, rhs::AbstractArray; kwargs...) - return latexalign(hcat(lhs, rhs); kwargs...) -end - -function _latexalign(lhs::Tuple, rhs::Tuple; kwargs...) - return latexalign(hcat(collect(lhs), collect(rhs)); kwargs...) -end - -_latexalign(args::Tuple...; kwargs...) = latexalign(reduce(hcat, [collect(i) for i in args]); kwargs...) - -_latexalign(arg::Tuple; kwargs...) = latexalign(reduce(hcat, [collect(i) for i in arg]); kwargs...) - -function _latexalign(nested::AbstractVector{AbstractVector}; kwargs...) - return latexalign(reduce(hcat, nested); kwargs...) -end - -function _latexalign(d::AbstractDict; kwargs...) - latexalign(collect(keys(d)), collect(values(d)); kwargs...) -end - -""" - _latexalign(vec::AbstractVector) - -Go through the elements, split at any = sign, pass on as a matrix. -""" -function _latexalign(vec::AbstractVector; kwargs...) - lvec = latexraw.(vec; kwargs...) - ## turn the array into a matrix - lmat = reduce(hcat, split.(lvec, " = ")) - ## turn the matrix ito arrays of left-hand-side, right-hand-side. - larr = [lmat[i,:] for i in 1:size(lmat, 1)] - length(larr) < 2 && error("Invalid intput to _latexalign().") - return latexalign( reduce(hcat, larr) ; kwargs...) -end diff --git a/src/latexarray.jl b/src/latexarray.jl deleted file mode 100644 index a6c1f1e2..00000000 --- a/src/latexarray.jl +++ /dev/null @@ -1,42 +0,0 @@ -# latexarray(args...; kwargs...) = latexify(args...;kwargs...,env=:array) -latexarray(args...; kwargs...) = _latexarray(args...;kwargs...) - -function _latexarray(arr::AbstractArray) - adjustment = getconfig(:adjustment) - transpose = getconfig(:transpose) - double_linebreak = getconfig(:double_linebreak) - starred = getconfig(:starred) - - transpose && (arr = permutedims(arr)) - rows = first(size(arr)) - columns = length(size(arr)) > 1 ? size(arr)[2] : 1 - - eol = double_linebreak ? " \\\\\\\\\n" : " \\\\\n" - - str = "\\left[\n" - str *= "\\begin{array}{" * "$(adjustment)"^columns * "}\n" - - arr = descend.(arr) - for i=1:rows, j=1:columns - str *= arr[i,j] - j==columns ? (str *= eol) : (str *= " & ") - end - - str *= "\\end{array}\n" - str *= "\\right]" - latexstr = LaTeXString(str) - # COPY_TO_CLIPBOARD && clipboard(latexstr) - return latexstr -end - - -_latexarray(args::AbstractArray...) = _latexarray(reduce(hcat, args)) -_latexarray(arg::AbstractDict) = _latexarray(collect(keys(arg)), collect(values(arg))) -_latexarray(arg::Tuple...) = _latexarray([collect(i) for i in arg]...) - -function _latexarray(arg::Tuple) - if first(arg) isa Tuple || first(arg) isa AbstractArray - return _latexarray([collect(i) for i in arg]...) - end - return _latexarray(collect(arg)) -end diff --git a/src/latexbracket.jl b/src/latexbracket.jl deleted file mode 100644 index 9465f256..00000000 --- a/src/latexbracket.jl +++ /dev/null @@ -1,9 +0,0 @@ -latexbracket(args...; kwargs...) = _latexbracket(args...; kwargs...) - -function _latexbracket(x; kwargs...) - latexstr = LaTeXString( "\\[\n" * latexraw(x; kwargs...) * "\\]\n") - COPY_TO_CLIPBOARD && clipboard(latexstr) - return latexstr -end - -_latexbracket(args...; kwargs...) = latexbracket(args; kwargs...) diff --git a/src/latexequation.jl b/src/latexequation.jl deleted file mode 100644 index 569fbd8b..00000000 --- a/src/latexequation.jl +++ /dev/null @@ -1,15 +0,0 @@ - -latexequation(args...; kwargs...) = _latexequation(args...; kwargs...) - -function _latexequation(eq; starred=false, kwargs...) - latexstr = latexraw(eq; kwargs...) - - str = "\\begin{equation$(starred ? "*" : "")}\n" - str *= latexstr - str *= "\n" - str *= "\\end{equation$(starred ? "*" : "")}\n" - latexstr = LaTeXString(str) - COPY_TO_CLIPBOARD && clipboard(latexstr) - return latexstr -end - diff --git a/src/latexinline.jl b/src/latexinline.jl deleted file mode 100644 index fe2a1083..00000000 --- a/src/latexinline.jl +++ /dev/null @@ -1,7 +0,0 @@ -latexinline(args...;kwargs...) = _latexinline(args...;kwargs...) - -function _latexinline(x; kwargs...) - latexstr = latexstring( latexify(x; kwargs...,env=:raw) ) - COPY_TO_CLIPBOARD && clipboard(latexstr) - return latexstr -end \ No newline at end of file diff --git a/src/latexraw_recipes.jl b/src/latexraw_recipes.jl deleted file mode 100644 index c5c3a34d..00000000 --- a/src/latexraw_recipes.jl +++ /dev/null @@ -1,348 +0,0 @@ - -const MATCHING_FUNCTIONS = [ - function report_bad_call(expr, prevop, config) - println(""" - Unsupported input with - expr=$expr - prevop=$prevop - and config=$config - """) - return nothing - end, - function call(expr, prevop, config) - if expr isa Expr && head(expr) == :call - h, op, args = unpack(expr) - - if op isa Symbol - funcname = string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) - else - funcname = descend(op) - end - if length(args) >= 1 && head(args[1]) == :parameters - _arg = "\\left( $(join(descend.(args[2:end]), ", ")); $(descend(args[1])) \\right)" - else - _arg = "\\left( " * join(descend.(args), ", ") * " \\right)" - end - return funcname * _arg - end - end, - function _sqrt(ex, prevop, config) - operation(ex) == :sqrt ? "\\$(operation(ex)){$(arguments(ex)[1])}" : nothing - end, - function _abs(ex, prevop, config) - operation(ex) == :abs ? "\\left\\|$(arguments(ex)[1])\\right\\|" : nothing - end, - function _single_comparison(ex, args...) - if operation(ex) ∈ keys(comparison_operators) && length(arguments(ex)) == 2 - str = "$(arguments(ex)[1]) $(comparison_operators[operation(ex)]) $(arguments(ex)[2])" - str = "\\left( $str \\right)" - return str - end - end, - function _if(ex, prevop, config) - if ex isa Expr && head(ex) == :if - str = build_if_else_body( - ex.args, - getconfig(:ifstr), - getconfig(:elseifstr), - getconfig(:elsestr) - ) - return """ - \\begin{cases} - $str - \\end{cases}""" - end - end, - function _elseif(ex, prevop, config) - if ex isa Expr && head(ex) == :elseif - str = build_if_else_body( - ex.args, - getconfig(:elseifstr), - getconfig(:elseifstr), - getconfig(:elsestr) - ) - end - end, - function _oneline_function(ex, prevop, config) - if head(ex) == :function && length(arguments(ex)) == 1 - return "$(descend(operation(ex), head(ex))) = $(descend(arguments(ex)[1], head(ex)))" - end - end, - function _return(ex, prevop, config) - head(ex) == :return && length(arguments(ex)) == 0 ? descend(operation(ex)) : nothing - end, - function _chained_comparisons(ex, _...) - if head(ex) == :comparison && Symbol.(arguments(ex)[1:2:end]) ⊆ keys(comparison_operators) - str = join([isodd(i) ? "$var" : comparison_operators[Symbol(var)] for (i, var) in enumerate(descend.(vcat(operation(ex), arguments(ex))))], " ") - str = "\\left( $str \\right)" - return str - end - end, - function _type_annotation(ex, prevop, config) - if head(ex) == :(::) - if length(arguments(ex)) == 0 - return "::$(operation(ex))" - elseif length(arguments(ex)) == 1 - return "$(operation(ex))::$(arguments(ex)[1])" - end - end - end, - function _wedge(ex, prevop, config) - head(ex) == :(&&) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\wedge $(descend(arguments(ex)[1]))" : nothing - end, - function _vee(ex, prevop, config) - head(ex) == :(||) && length(arguments(ex)) == 1 ? "$(descend(operation(ex))) \\vee $(descend(arguments(ex)[1]))" : nothing - end, - function _negation(ex, prevop, config) - operation(ex) == :(!) ? "\\neg $(arguments(ex)[1])" : nothing - end, - function _kw(x, args...) - head(x) == :kw ? "$(descend(operation(x))) = $(descend(arguments(x)[1]))" : nothing - end, - function _parameters(x, args...) - head(x) == :parameters ? join(descend.(vcat(operation(x), arguments(x))), ", ") : nothing - end, - function _indexing(x, prevop, config) - if head(x) == :ref - if getconfig(:index) == :subscript - return "$(operation(x))_{$(join(arguments(x), ","))}" - elseif getconfig(:index) == :bracket - argstring = join(descend.(arguments(x)), ", ") - return "$(descend(operation(x)))\\left[$argstring\\right]" - else - throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket")) - end - end - end, - function _broadcast_macro(ex, prevop, config) - if head(ex) == :macrocall && operation(ex) == Symbol("@__dot__") - return descend(arguments(ex)[end]) - end - end, - function _block(x, args...) - if head(x) == :block - return descend(vcat(operation(x), arguments(x))[end]) - end - end, - function number(x, prevop, config) - if x isa Number - try isinf(x) && return "\\infty" catch; end - fmt = getconfig(:fmt) - fmt isa String && (fmt = PrintfNumberFormatter(fmt)) - str = string(fmt(x)) - sign(x) == -1 && prevop == :^ && (str = surround(str)) - return str - end - end, - function rational_expr(x, prevop, config) - if operation(x) == :// - if arguments(x)[2] == 1 - return descend(arguments[1], prevop) - else - descend(:($(arguments(x)[1])/$(arguments(x)[2])), prevop) - end - end - end, - function rational(x, prevop, config) - if x isa Rational - str = x.den == 1 ? descend(x.num, prevop) : descend(:($(x.num)/$(x.den)), prevop) - prevop ∈ [:*, :^] && (str = surround(str)) - return str - end - end, - function complex(z, prevop, config) - if z isa Complex - str = "$(descend(z.re))$(z.im < 0 ? "-" : "+" )$(descend(abs(z.im)))\\textit{i}" - prevop ∈ [:*, :^] && (str = surround(str)) - return str - end - end, - function _missing(x, prevop, config) - if ismissing(x) - "\\textrm{NA}" - end - end, - function _nothing(x, prevop, config) - x === nothing ? "" : nothing - end, - function symbol(sym, _, config) - if sym isa Symbol - str = string(sym == :Inf ? :∞ : sym) - str = convert_subscript(str) - getconfig(:convert_unicode) && (str = unicode2latex(str)) - return str - end - end, - function _char(c, args...) - c isa Char ? string(c) : nothing - end, - function array(x, args...) - x isa AbstractArray ? _latexarray(x) : nothing - end, - function tuple(x, args...) - x isa Tuple ? _latexarray(x) : nothing - end, - function vect_exp(x, args...) - head(x) ∈ [:vect, :vcat] ? _latexarray(expr_to_array(x)) : nothing - end, - function hcat_exp(x, args...) - # head(x)==:hcat ? _latexarray(permutedims(vcat(operation(x), arguments(x)))) : nothing - head(x)==:hcat ? _latexarray(expr_to_array(x)) : nothing - end, - (expr, prevop, config) -> begin - h, op, args = unpack(expr) - # if (expr isa LatexifyOperation || h == :LatexifyOperation) && op == :merge - if h == :call && op == :latexifymerge - join(descend.(args), "") - end - end, - function parse_string(str, prevop, config) - if str isa AbstractString - try - ex = Meta.parse(str) - return descend(ex, prevop) - catch ParseError - error(""" - in Latexify.jl: - You are trying to create latex-maths from a `String` that cannot be parsed as - an expression. - - `latexify` will, by default, try to parse any string inputs into expressions - and this parsing has just failed. - - If you are passing strings that you want returned verbatim as part of your input, - try making them `LaTeXString`s first. - - If you are trying to make a table with plain text, try passing the keyword - argument `latex=false`. You should also ensure that you have chosen an output - environment that is capable of displaying not-maths objects. Try for example - `env=:table` for a latex table or `env=:mdtable` for a markdown table. - """) - end - end - end, - function _pass_through_LaTeXString_substrings(str, args...) - str isa SubString{LaTeXString} ? String(str) : nothing - end, - function _pass_through_LaTeXString(str, args...) - str isa LaTeXString ? str.s : nothing - end, - function _tuple_expr(expr, prevop, config) - head(expr) == :tuple ? join(vcat(operation(expr), arguments(expr)), ", ") : nothing - end, - function strip_broadcast_dot(expr, prevop, config) - h, op, args = unpack(expr) - if expr isa Expr && config[:strip_broadcast] && h == :call && startswith(string(op), '.') - return string(descend(Expr(h, Symbol(string(op)[2:end]), args...), prevop)) - end - end, - function strip_broadcast_dot_call(expr, prevop, config) - h, op, args = unpack(expr) - if expr isa Expr && config[:strip_broadcast] && h == :. - return descend(Expr(:call, op, args[1].args...), prevop) - end - end, - function plusminus(expr, prevop, config) - h, op, args = unpack(expr) - if h == :call && op == :± - return "$(descend(args[1], op)) \\pm $(descend(args[2], op))" - end - end, - function division(expr, prevop, config) - h, op, args = unpack(expr) - if h == :call && op == :/ - "\\frac{$(descend(args[1], op))}{$(descend(args[2], op))}" - end - end, - function multiplication(expr, prevop, config) - h, op, args = unpack(expr) - if h == :call && op == :* - join(descend.(args, op), "$(config[:mulsym])") - end - end, - function addition(expr, prevop, config) - h, op, args = unpack(expr) - if h == :call && op == :+ - str = join(descend.(args, op), " + ") - str = replace(str, "+ -"=>"-") - prevop ∈ [:*, :^] && (str = surround(str)) - return str - end - end, - function subtraction(expr, prevop, config) - # this one is so gnarly because it tries to fix stuff like - - or -(-(x-y)) - # -(x) is also a bit different to -(x, y) which does not make things simpler - h, op, args = unpack(expr) - if h == :call && op == :- - if length(args) == 1 - if operation(args[1]) == :- && length(arguments(args[1])) == 1 - return descend(arguments(args[1])[1], prevop) - elseif args[1] isa Number && sign(args[1]) == -1 - # return _latexraw(-args[1]; config...) - return descend(-args[1], op) - else - _arg = operation(args[1]) ∈ [:-, :+, :±] ? surround(args[1]) : args[1] - return prevop == :^ ? surround("$op$_arg") : "$op$_arg" - end - - elseif length(args) == 2 - if args[2] isa Number && sign(args[2]) == -1 - return "$(descend(args[1], :+)) + $(descend(-args[2], :+))" - end - if operation(args[2]) == :- && length(arguments(args[2])) == 1 - return "$(descend(args[1], :+)) + $(descend(arguments(args[2])[1], :+))" - end - if operation(args[2]) ∈ [:-, :.-, :+, :.+] - return "$(descend(args[1], op)) - $(surround(descend(args[2], op)))" - end - str = join(descend.(args, op), " - ") - prevop ∈ [:*, :^] && (str = surround(str)) - return str - end - end - end, - function pow(expr, prevop, config) - h, op, args = unpack(expr) - if h == :call && op == :^ - if operation(args[1]) in Latexify.trigonometric_functions - fsym = operation(args[1]) - fstring = get(Latexify.function2latex, fsym, "\\$(fsym)") - "$fstring^{$(descend(args[2], op))}\\left( $(join(descend.(arguments(args[1]), operation(args[1])), ", ")) \\right)" - else - "$(descend(args[1], op))^{$(descend(args[2], Val{:NoSurround}()))}" - end - end - end, - function equals(expr, prevop, config) - if head(expr) == :(=) - return "$(descend(expr.args[1], expr.head)) = $(descend(expr.args[2], expr.head))" - end - end, - function l_funcs(ex, prevop, config) - if head(ex) == :call && startswith(string(operation(ex)), "l_") - l_func = string(operation(ex))[3:end] - return "\\$(l_func){$(join(descend.(arguments(ex), prevop), "}{"))}" - end - end, -] - - -function build_if_else_body(args, ifstr, elseifstr, elsestr) - _args = filter(x -> !(x isa LineNumberNode), args) - dargs = descend.(_args) - str = if length(_args) == 2 - """ - $(dargs[2]) & $ifstr $(dargs[1])""" - elseif length(_args) == 3 && head(_args[end]) == :elseif - """ - $(dargs[2]) & $ifstr $(dargs[1])\\\\ - $(dargs[3])""" - elseif length(_args) == 3 - """ - $(dargs[2]) & $ifstr $(dargs[1])\\\\ - $(dargs[3]) & $elsestr""" - else - throw(ArgumentError("Unexpected if/elseif/else statement to latexify. This could well be a Latexify.jl bug.")) - end - return str -end \ No newline at end of file diff --git a/src/latextabular.jl b/src/latextabular.jl deleted file mode 100644 index 4714c2ef..00000000 --- a/src/latextabular.jl +++ /dev/null @@ -1,56 +0,0 @@ -latextabular(args...; kwargs...) = _latextabular(args...; kwargs...) - -function _latextabular(arr::AbstractMatrix; latex::Bool=true, booktabs::Bool=false, head=[], side=[], adjustment::Symbol=:c, transpose=false, kwargs...) - transpose && (arr = permutedims(arr, [2,1])) - - if !isempty(head) - arr = vcat(reduce(hcat, head), arr) - @assert length(head) == size(arr, 2) "The length of the head does not match the shape of the input matrix." - end - if !isempty(side) - length(side) == size(arr, 1) - 1 && (side = [""; side]) - @assert length(side) == size(arr, 1) "The length of the side does not match the shape of the input matrix." - arr = hcat(side, arr) - end - - (rows, columns) = size(arr) - str = "\\begin{tabular}{" * "$(adjustment)"^columns * "}\n" - - if booktabs - str *= "\\toprule\n" - end - - if latex - arr = latexinline.(arr; kwargs...) - elseif haskey(kwargs, :fmt) - formatter = kwargs[:fmt] isa String ? PrintfNumberFormatter(kwargs[:fmt]) : kwargs[:fmt] - arr = map(x -> x isa Number ? formatter(x) : x, arr) - end - - # print first row - str *= join(arr[1,:], " & ") - str *= "\\\\\n" - - if booktabs && !isempty(head) - str *= "\\midrule\n" - end - - for i in 2:size(arr, 1) - str *= join(arr[i,:], " & ") - str *= "\\\\\n" - end - - if booktabs - str *= "\\bottomrule\n" - end - - str *= "\\end{tabular}\n" - latexstr = LaTeXString(str) - COPY_TO_CLIPBOARD && clipboard(latexstr) - return latexstr -end - - -_latextabular(vec::AbstractVector; kwargs...) = latextabular(reduce(hcat, vec); kwargs...) -_latextabular(vectors::AbstractVector...; kwargs...) = latextabular(reduce(hcat, vectors); kwargs...) -_latextabular(dict::AbstractDict; kwargs...) = latextabular(hcat(collect(keys(dict)), collect(values(dict))); kwargs...) diff --git a/test/latexinline_test.jl b/test/latexinline_test.jl index 93beb271..8a0894a3 100644 --- a/test/latexinline_test.jl +++ b/test/latexinline_test.jl @@ -2,7 +2,7 @@ using Latexify using LaTeXStrings test_array = ["x/y * d" :x ; :( (t_sub_sub - x)^(2*p) ) 3//4 ] -@test latexinline.(test_array) == [ +@test latexify.(test_array) == [ L"$\frac{x}{y} \cdot d$" L"$x$" ; L"$\left( t_{sub\_sub} - x \right)^{2 \cdot p}$" L"$\frac{3}{4}$" ] From 2f285ffe04ec2a4ec882ffe26829b9434c344ec8 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 6 Sep 2022 10:37:17 +0200 Subject: [PATCH 38/40] Rename file with latexification instructions. --- src/Latexify.jl | 2 +- src/{matching_functions_test.jl => instructions.jl} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{matching_functions_test.jl => instructions.jl} (100%) diff --git a/src/Latexify.jl b/src/Latexify.jl index 1ed73cb2..dc6008b2 100644 --- a/src/Latexify.jl +++ b/src/Latexify.jl @@ -31,7 +31,7 @@ include("latexraw.jl") include("default_kwargs.jl") include("recipes.jl") include("macros.jl") -include("matching_functions_test.jl") +include("instructions.jl") # include("mdtable.jl") # include("mdtext.jl") diff --git a/src/matching_functions_test.jl b/src/instructions.jl similarity index 100% rename from src/matching_functions_test.jl rename to src/instructions.jl From e68b147ca51ad91ac9ce2a76f6bbf23b6d56fcf2 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 13 Sep 2022 14:24:06 +0200 Subject: [PATCH 39/40] Support latexifying macros in exprs. --- src/instructions.jl | 23 +++++++++++++++++++++++ test/macros.jl | 2 ++ 2 files changed, 25 insertions(+) diff --git a/src/instructions.jl b/src/instructions.jl index f3039598..6f1a032a 100644 --- a/src/instructions.jl +++ b/src/instructions.jl @@ -19,6 +19,22 @@ DEFAULT_INSTRUCTIONS = Function[ end return false end, + function macrocall(io, expr, config, rules, prevop) + expr isa Expr && head(expr) == :macrocall || return false + h, op, args = unpack(expr) + if op isa Symbol + funcname = replace(string(op), "_"=>"\\_") + # funcname = string(get(Latexify.function2latex, op, replace(string(op), "_"=>"\\_"))) + write(io, funcname) + else + descend(io, op, config, rules, nothing) + end + write(io, "\\left( ") + !isempty(args) && join_descend(io, args, config, rules, ", ") + write(io, " \\right)") + # _arg = "\\left( " * join(descend.(args), ", ") * " \\right)" + return true + end, function call(io::IO, expr, config, rules, prevop) if expr isa Expr && head(expr) == :call h, op, args = unpack(expr) @@ -213,6 +229,7 @@ DEFAULT_INSTRUCTIONS = Function[ function number(io::IO, x, config, rules, prevop) if x isa Number if hasmethod(isinf, Tuple{typeof(x)}) && isinf(x) + sign(x) == -1 && write(io, "-") write(io, "\\infty") return true end @@ -541,6 +558,12 @@ DEFAULT_INSTRUCTIONS = Function[ end return false end, + function strip_linenumbernode(io, expr, config, rules, prevop) + h, op, args = unpack(expr) + expr isa Expr && any(map(x->x isa LineNumberNode, args)) || return false + descend(io, Expr(head(expr), op, filter(x->!(x isa LineNumberNode), args)...), config, rules, prevop) + return true + end, ] function build_if_else_body(io::IO, args, config, rules, prevop=nothing; ifstr =get(config, :ifstr, "if"), elsestr= get(config, :elsestr, "otherwise")) diff --git a/test/macros.jl b/test/macros.jl index 7827709d..6bcfe759 100644 --- a/test/macros.jl +++ b/test/macros.jl @@ -14,3 +14,5 @@ l3 = @latexify dummyfunc2(x::Number; y=1, z=3) = x^2/y + z l4 = @latexify dummyfunc2(::Number; y=1, z=3) = x^2/y + z @test l4 == raw"$dummyfunc2\left( ::\mathrm{Number}; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" +@test latexify(:(@hi(x / y))) == replace( +raw"$@hi\left( \frac{x}{y} \right)$", "\r\n"=>"\n") From 29a671104b38b3817beeceab71cbf424f5321732 Mon Sep 17 00:00:00 2001 From: Niklas Korsbo Date: Tue, 13 Sep 2022 14:24:29 +0200 Subject: [PATCH 40/40] Add broken test A reminder of what does not work. --- test/latextabular_test.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/latextabular_test.jl b/test/latextabular_test.jl index 740e1c75..116c0154 100644 --- a/test/latextabular_test.jl +++ b/test/latextabular_test.jl @@ -11,6 +11,13 @@ raw"\begin{tabular}{ccc} 3 & 13 & Z \end{tabular}", "\r\n"=>"\n") +# Latexify.@generate_test latexify([1.]; env=:table) +@test_broken latexify([1.0]; env = :table) == replace( +raw"\begin{tabular}{c} +$1.0$\\ +\end{tabular} +", "\r\n"=>"\n") + arr = ["x/(y-1)", 1.0, 3//2, :(x-y), :symb] M = (vcat(reduce(hcat, arr), reduce(hcat, arr)))