Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursion #8

Merged
merged 11 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

julia_version = "1.10.0"
manifest_format = "2.0"
project_hash = "9288745b0c141040f1c8126efff08e5e2d46709d"
project_hash = "0e345527edcf441c2de8a2d1c00b2b6466582291"

[[deps.AbstractTrees]]
git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177"
uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
version = "0.4.5"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
Expand Down Expand Up @@ -185,9 +190,9 @@ uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"

[[deps.TestHandcalcFunctions]]
git-tree-sha1 = "3df775c88fa815d10b9ca754e276256fa78cb41d"
git-tree-sha1 = "1e4ebeb8d7d78f9974c0153321e80fa66487d3bf"
uuid = "6ba57fb7-81df-4b24-8e8e-a3885b6fcae7"
version = "0.1.0"
version = "0.2.0"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
Expand Down
5 changes: 3 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name = "Handcalcs"
uuid = "e8a07092-c156-4455-ab8e-ed8bc81edefb"
authors = ["Cole Miller"]
version = "0.2.1"
version = "0.3.0"

[deps]
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Expand All @@ -18,7 +19,7 @@ LaTeXStrings = "1.3"
Latexify = "0.16"
MacroTools = "0.5"
Revise = "3.5"
TestHandcalcFunctions = "0.1"
TestHandcalcFunctions = "0.2"
Unitful = "1.19"
UnitfulLatexify = "1.6"
julia = "1.10"
Expand Down
49 changes: 45 additions & 4 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This is the documentation for [Handcalcs.jl](https://github.com/co1emi11er2/Hand

**Note: This package now renders properly in Quarto/Weave!! You can change the default settings to your liking. See examples below.**

## Examples
## Expression Examples

### A single expression example:

Expand Down Expand Up @@ -65,19 +65,60 @@ a, b, c = 1, 2, 3
z = 6
end cols=3 spa=0
```
## Function Examples

The `@handcalcs` macro will now automatically try to "unroll" the expressions within a function when the expression has the following pattern: `variable = function_name(args...; kwargs...)`. Note that this is recursive, so if you have a function that calls other functions where the expressions that call the function are of the format mentioned, it will continue to step into each function to "unroll" all expressions.

One issue that can arise are for the functions that you do not want to unroll. Consider the expression: `y = sin(x)` or ` y = x + 5`. Both of these expressions match the format: `variable = function_name(args...; kwargs...)` and would be unrolled. This would result in an error since these functions don't have generic math expressions that can be latexified defining the function. You will need to use the `not_funcs` keyword to manually tell the @handcalcs macro to pass over these functions. Some of the common math functions that you will not want to unroll are automatically passed over. See examples below.

### An example for rendering expressions within a function:

```@example other
function calc_Ix(b, h) # function defined in TestHandcalcFunctions
Ix = b*h^3/12
return Ix
end;
```

```@example main
using TestHandcalcFunctions
b = 5 # width
h = 15 # height
@handfunc Ix = calc_Ix(b, h) # function is defined in TestHandcalcFunctions package
@handcalcs Ix = calc_Ix(b, h) # function is defined in TestHandcalcFunctions package
```

The `Ix` variable is evaluated. Ix being the variable assigned in the @handfunc part (variables within function are not defined in the global name space). If you assign it to a different variable then that will be the variable defined (although you will still see it as Ix in the latex portion). Also note that return statements are filtered out of the function body, so keep relevant parts separate from return statements.
The `Ix` variable is evaluated. Ix being the variable assigned in the @handcalcs part (variables within function are not defined in the global name space). If you assign it to a different variable then that will be the variable defined (although you will still see it as Ix in the latex portion). Also note that return statements are filtered out of the function body, so keep relevant parts separate from return statements.

```@example other
function calc_Is(b, h) # function defined in TestHandcalcFunctions
Ix = calc_Ix(b, h)
Iy = calc_Iy(h, b)
return Ix, Iy
end;
```

```@example main
using TestHandcalcFunctions
x = 0
@handcalcs begin
y = sin(x)
z = cos(x)
I_x, I_y = TestHandcalcFunctions.calc_Is(5, 15)
end not_funcs = [sin cos]
```

In the above example `sin` and `cos` were passed over and calc_Is was not. As you can see, the calc_Is function was a function that called other functions, and the @handcalcs macro continued to step into each function to unroll all expressions. Please see below for a list of the current functions that are passed over automatically. Please submit a pull request if you would like to add more generic math functions that I have left out.

```
const math_syms = [
:*, :/, :^, :+, :-, :%,
:.*, :./, :.^, :.+, :.-, :.%,
:<, :>, Symbol(==), :<=, :>=,
:.<, :.>, :.==, :.<=, :.>=,
:sqrt, :sin, :cos, :tan]
```

Current Limitations for `@handfunc`
Current Limitations for `@handcalcs`

- I believe the function needs to be defined in another package. The @code_expr macro from CodeTracking.jl does not see functions in Main for some reason.
- If the function has other function calls within it's body that are not available in Main, then the macro will error.
Expand Down
10 changes: 8 additions & 2 deletions src/Handcalcs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ using MacroTools
using LaTeXStrings
using CodeTracking, Revise
using InteractiveUtils
import AbstractTrees: Leaves

export @handcalc, @handcalcs, @handfunc, multiline_latex
export @handcalc, @handcalcs, @handfunc, multiline_latex, collect_exprs
export set_handcalcs, reset_handcalcs, get_handcalcs #, initialize_format
export latexify, @latexdefine, set_default, get_default, reset_default

Expand All @@ -20,7 +21,12 @@ export latexify, @latexdefine, set_default, get_default, reset_default
# set_default(fmt=x->format(round(x, digits=4)))
# end
# end
const math_syms = [:*, :/, :^, :+, :-, :%, :.*, :./, :.^, :.+, :.-, :.%, :sqrt, :sin, :cos, :tan]
const math_syms = [
:*, :/, :^, :+, :-, :%,
:.*, :./, :.^, :.+, :.-, :.%,
:<, :>, Symbol(==), :<=, :>=,
:.<, :.>, :.==, :.<=, :.>=,
:sqrt, :sin, :cos, :tan]
const h_syms = [:cols, :spa, :h_env]

include("default_h_kwargs.jl")
Expand Down
86 changes: 66 additions & 20 deletions src/handcalc_marco.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"""
@handcalc expression

Create `LaTeXString` representing `expression`. The expression being a vaiable followed by an equals sign and an algebraic equation.
Create `LaTeXString` representing `expression`. The expression being a vaiable followed by
an equals sign and an algebraic equation.
Any side effects of the expression, like assignments, are evaluated as well.
The RHS can be formatted or otherwise transformed by supplying a function as kwarg `post`.

Expand All @@ -21,14 +22,14 @@
"""
macro handcalc(expr, kwargs...)
expr = unblock(expr)
expr = rmlines(expr)
# if @capture(expr, x_ = f_(fields__) | f_(fields__)) #future recursion
# if f ∉ math_syms
# return esc(:((@macroexpand @handfunc $expr)))
# end
# # return :($esc(@handfunc $expr))
# end
expr = rmlines(expr)
if @capture(expr, x_ = f_(fields__) | f_(fields__)) # Check if function call
if f ∉ math_syms && check_not_funcs(f, kwargs)
kwargs = kwargs..., :(is_recursive = true)
return esc(:(@handfunc $(expr) $(kwargs...)))
end
end

expr_post = expr.head == :(=) ? expr.args[2:end] : expr
expr_numeric = _walk_expr(expr_post, math_syms)
params = _extractparam.(kwargs)
Expand All @@ -53,12 +54,12 @@
Expr(
:call,
:latexify,
Expr(:parameters, _extractparam.(kwargs)...),
Expr(:call, :Expr,
QuoteNode(:(=)), Meta.quot(expr), # symbolic portion
Expr(:call, :Expr,
QuoteNode(:(=)), Meta.quot(expr_numeric), # numeric portion
Expr(:call, post, _executable(expr)))), # defines variable
Expr(:parameters, _extractparam.(kwargs)...),
Expr(:call, :Expr,
QuoteNode(:(=)), Meta.quot(expr), # symbolic portion
Expr(:call, :Expr,
QuoteNode(:(=)), Meta.quot(expr_numeric), # numeric portion
Expr(:call, post, _executable(expr)))), # defines variable
),
)
end
Expand All @@ -77,7 +78,7 @@
end

_extractparam(arg::Symbol) = arg
_extractparam(arg::Expr) = Expr(:kw, arg.args[1], arg.args[2])
_extractparam(arg::Expr) = Expr(:kw, arg.args[1], arg.args[2])

# ***************************************************

Expand All @@ -88,7 +89,7 @@
# ***************************************************

function numeric_sub(x)
Expr(:($), x)
Expr(:($), x)
end

function _walk_expr(expr::Vector, math_syms)
Expand All @@ -110,7 +111,7 @@
count = 1
return numeric_sub(ex)
end
return ex
return ex
end
end

Expand All @@ -133,7 +134,7 @@
count = 1
return numeric_sub(ex)
end
return ex
return ex
end
end

Expand All @@ -147,4 +148,49 @@
end

# ***************************************************
# ***************************************************
# ***************************************************

function check_not_funcs(f, kwargs)
not_funcs = find_not_funcs(kwargs)
not_funcs = typeof(not_funcs) == Symbol ? [not_funcs] : not_funcs
return f ∉ not_funcs
end

function find_not_funcs(kwargs)
not_funcs = []
for kwarg in kwargs
split_kwarg = _split_kwarg(kwarg)
if split_kwarg == :not_funcs
not_funcs = parse_not_funcs(kwarg.args[2])
end
end
return not_funcs
end

function parse_not_funcs(value::QuoteNode)
[value.value]
end

function parse_not_funcs(value::Symbol)
[value]
end

function parse_not_funcs(expr::Expr)
not_funcs = []
if expr.head in [:vcat :hcat :vect]
for arg in expr.args
push!(not_funcs, parse_not_func(arg))
end
elseif expr.head == :.
return [expr]

Check warning on line 185 in src/handcalc_marco.jl

View check run for this annotation

Codecov / codecov/patch

src/handcalc_marco.jl#L184-L185

Added lines #L184 - L185 were not covered by tests
end
return not_funcs
end

function parse_not_func(value::QuoteNode)
value.value
end

function parse_not_func(value)
value

Check warning on line 195 in src/handcalc_marco.jl

View check run for this annotation

Codecov / codecov/patch

src/handcalc_marco.jl#L195

Added line #L195 was not covered by tests
end
50 changes: 38 additions & 12 deletions src/handcalcs_macro.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"""
@handcalcs expressions

Create `LaTeXString` representing `expressions`. The expressions representing a number of expressions.
A single expression being a vaiable followed by an equals sign and an algebraic equation.
Any side effects of the expression, like assignments, are evaluated as well.
Create `LaTeXString` representing `expressions`. The expressions representing a number of
expressions. A single expression being a vaiable followed by an equals sign and an algebraic
equation. Any side effects of the expression, like assignments, are evaluated as well.
The RHS can be formatted or otherwise transformed by supplying a function as kwarg `post`.
Can also add comments to the end of equations. See example below.

Expand Down Expand Up @@ -43,20 +43,19 @@ julia> d
macro handcalcs(expr, kwargs...)
expr = unblock(expr)
expr = rmlines(expr)
h_kwargs, kwargs = clean_kwargs(kwargs) # parse handcalc kwargs (h_kwargs)

is_recursive, h_kwargs, kwargs = clean_kwargs(kwargs) # parse handcalc kwargs (h_kwargs)
exprs = [] #initialize expression accumulator

# If singular symbol
if typeof(expr) == Symbol
push!(exprs, :(@latexdefine $(expr) $(kwargs...)))
return _handcalcs(exprs, h_kwargs)
return is_recursive ? _handcalcs_recursive(exprs) : _handcalcs(exprs, h_kwargs)
end

# If singular expression
if expr.head == :(=)
push!(exprs, :(@handcalc $(expr) $(kwargs...)))
return _handcalcs(exprs, h_kwargs)
return is_recursive ? _handcalcs_recursive(exprs) : _handcalcs(exprs, h_kwargs)
end

# If multiple Expressions
Expand All @@ -72,7 +71,7 @@ macro handcalcs(expr, kwargs...)
error("Code pieces should be of type string or expression")
end
end
return _handcalcs(exprs, h_kwargs)
return is_recursive ? _handcalcs_recursive(exprs) : _handcalcs(exprs, h_kwargs)
end

function _handcalcs(exprs, h_kwargs)
Expand All @@ -81,6 +80,20 @@ function _handcalcs(exprs, h_kwargs)
Expr(:parameters, _extractparam.(h_kwargs)...), exprs...)))
end

function _handcalcs_recursive(exprs)
Expr(:block, esc(
Expr(:call, :collect_exprs,
exprs...)))
end

function collect_exprs(exprs...) # just collect when exprs recursive
exprs_array = []
for expr in exprs
push!(exprs_array, expr)
end
return exprs_array
end

function multiline_latex(exprs...; kwargs...)
h_kwargs = merge(default_h_kwargs, kwargs)
return process_multiline_latex(exprs...;h_kwargs...)
Expand All @@ -93,23 +106,36 @@ function clean_expr(expr)
expr = replace(expr, "="=>"&=", count=1) # add alignment
end

function clean_kwargs(kwargs)
# Splits handcalc kwargs and latexify kwargs
# Also checks for recursion (if @handcalcs is being called from @handcalc)
function clean_kwargs(kwargs)
is_recursive = false
h_kwargs = []
l_kwargs = []
for kwarg in kwargs
if _split_kwarg(kwarg) in h_syms
split_kwarg = _split_kwarg(kwarg)
if split_kwarg in h_syms
h_kwargs = push!(h_kwargs, kwarg)
elseif split_kwarg == :is_recursive
is_recursive = true
else
l_kwargs = push!(l_kwargs, kwarg)
end
end
return Tuple(h_kwargs), Tuple(l_kwargs)
return is_recursive, Tuple(h_kwargs), Tuple(l_kwargs)
end

_split_kwarg(arg::Symbol) = arg
_split_kwarg(arg::Expr) = arg.args[1]

function process_multiline_latex(exprs...;cols=1, spa=10, h_env="aligned", kwargs...)
function process_multiline_latex(
exprs...;
cols=1,
spa=10,
h_env="aligned",
kwargs...
)
exprs = collect(Leaves(exprs)) # This handles nested vectors when recursive
cols_start = cols
multi_latex = "\\begin{$h_env}"
for (i, expr) in enumerate(exprs)
Expand Down
Loading
Loading