From 87df76ecca9e72a1f9857c3b42a58074ad68ed6e Mon Sep 17 00:00:00 2001 From: c42f Date: Tue, 8 Nov 2022 17:09:29 +1000 Subject: [PATCH 1/8] Playing with currying, chaining and underscores A super hacky, quick implementation of some ideas from https://discourse.julialang.org/t/fixing-the-piping-chaining-issue Parse chains of `/>` and `\>` at the rough same precedence as `|>`, but treat them as a "frontfix/backfix operator" for function calls such that the succeeding function call becomes curried in first or last argument. Thus, the following x /> f(y) \> g(z) is parsed as (chain x (/> f y) (\> g z)) and lowered to the equivalent of chain(x, a->f(a, y), b->g(z, b)) Also add lowering of underscore as strictly tight-binding placeholder syntax. (Super hacky - more forms should be allowed! This is just for experimentation). --- src/expr.jl | 42 +++++++++++++++++++++++++++++++++++++++++- src/kinds.jl | 3 +++ src/parser.jl | 24 +++++++++++++++++++++++- src/tokenize.jl | 4 ++++ 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/expr.jl b/src/expr.jl index 61ec2e75..3ce17925 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -11,6 +11,22 @@ function is_stringchunk(node) return k == K"String" || k == K"CmdString" end +function lower_underscores(args, skiparg=-1) + g = nothing + for i in 1:length(args) + if i == skiparg + continue + end + if args[i] == :_ + if isnothing(g) + g = gensym() + end + args[i] = g + end + end + return g +end + function reorder_parameters!(args, params_pos) p = 0 for i = length(args):-1:1 @@ -131,7 +147,7 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, args[2] = _to_expr(node_args[2]) else eq_to_kw_in_call = - ((headsym == :call || headsym == :dotcall) && is_prefix_call(node)) || + ((headsym == :call || headsym == :dotcall || headsym == Symbol("/>")) && is_prefix_call(node)) || headsym == :ref eq_to_kw_all = headsym == :parameters && !map_kw_in_params in_vcbr = headsym == :vect || headsym == :curly || headsym == :braces || headsym == :ref @@ -169,6 +185,7 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, headsym = Symbol("'") end # Move parameters blocks to args[2] + g = lower_underscores(args, 1) reorder_parameters!(args, 2) if headsym === :dotcall if is_prefix_call(node) @@ -179,9 +196,17 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, args[1] = Symbol(".", args[1]) end end + if !isnothing(g) + return Expr(:->, g, Expr(:call, args...)) + end elseif headsym in (:ref, :curly) # Move parameters blocks to args[2] reorder_parameters!(args, 2) + elseif headsym == :. + g = lower_underscores(args) + if !isnothing(g) + return Expr(:->, g, Expr(:., args...)) + end elseif headsym in (:tuple, :vect, :braces) # Move parameters blocks to args[1] reorder_parameters!(args, 1) @@ -299,10 +324,25 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, args[1] = Expr(headsym, args[1].args...) headsym = :const end + elseif headsym == Symbol("/>") + freearg = gensym() + cargs = [args[1], freearg, args[2:end]...] + reorder_parameters!(cargs, 2) + return Expr(:->, freearg, Expr(:call, cargs...)) + elseif headsym == Symbol("\\>") + freearg = gensym() + cargs = [args[1], args[2:end]..., freearg] + reorder_parameters!(cargs, 2) + return Expr(:->, freearg, Expr(:call, cargs...)) + elseif headsym == :chain + return Expr(:call, :(JuliaSyntax.chain), args...) end return Expr(headsym, args...) end +chain(x, f, fs...) = chain(f(x), fs...) +chain(x) = x + Base.Expr(node::SyntaxNode) = _to_expr(node) function build_tree(::Type{Expr}, stream::ParseStream; kws...) diff --git a/src/kinds.jl b/src/kinds.jl index 3b5fa1bd..95a7b689 100644 --- a/src/kinds.jl +++ b/src/kinds.jl @@ -823,6 +823,8 @@ const _kind_names = "'" ".'" "->" + "/>" + "\\>" "BEGIN_UNICODE_OPS" "¬" @@ -878,6 +880,7 @@ const _kind_names = "block" "call" "dotcall" + "chain" "comparison" "curly" "inert" # QuoteNode; not quasiquote diff --git a/src/parser.jl b/src/parser.jl index 6318054a..b2256e36 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -820,7 +820,29 @@ end # x .|> y ==> (dotcall-i x |> y) # flisp: parse-pipe> function parse_pipe_gt(ps::ParseState) - parse_LtoR(ps, parse_range, is_prec_pipe_gt) + parse_LtoR(ps, parse_curry_chain, is_prec_pipe_gt) +end + +# x /> f(y) /> g(z) ==> (chain x (/> f y) (/> g z)) +# x /> A.f(y) ==> (chain x (/> (. A (quote f)) y)) +function parse_curry_chain(ps::ParseState) + mark = position(ps) + parse_range(ps) + has_chain = false + while (k = peek(ps); k == K"/>" || k == K"\>") + bump(ps, TRIVIA_FLAG) + m = position(ps) + parse_range(ps) + if peek_behind(ps).kind == K"call" + has_chain = true + reset_node!(ps, position(ps), kind=k) + else + emit(ps, m, K"error", error="Expected call to the right of />") + end + end + if has_chain + emit(ps, mark, K"chain") + end end # parse ranges and postfix ... diff --git a/src/tokenize.jl b/src/tokenize.jl index 82cab123..c4319726 100644 --- a/src/tokenize.jl +++ b/src/tokenize.jl @@ -932,6 +932,8 @@ function lex_forwardslash(l::Lexer) end elseif accept(l, '=') return emit(l, K"/=") + elseif accept(l, '>') + return emit(l, K"/>") else return emit(l, K"/") end @@ -940,6 +942,8 @@ end function lex_backslash(l::Lexer) if accept(l, '=') return emit(l, K"\=") + elseif accept(l, '>') + return emit(l, K"\>") end return emit(l, K"\\") end From 612b0c56847cb6486b531899c49a7491d536bb5b Mon Sep 17 00:00:00 2001 From: c42f Date: Wed, 9 Nov 2022 17:30:59 +1000 Subject: [PATCH 2/8] Make standalone prefix /> work. Improved lowering. --- src/expr.jl | 69 +++++++++++++++++++++++++++++++++++++++++++-------- src/parser.jl | 16 ++++++------ 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/expr.jl b/src/expr.jl index 3ce17925..fbfcdcaa 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -324,25 +324,74 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, args[1] = Expr(headsym, args[1].args...) headsym = :const end - elseif headsym == Symbol("/>") + elseif headsym == Symbol("/>") || headsym == Symbol("\\>") freearg = gensym() - cargs = [args[1], freearg, args[2:end]...] - reorder_parameters!(cargs, 2) - return Expr(:->, freearg, Expr(:call, cargs...)) - elseif headsym == Symbol("\\>") - freearg = gensym() - cargs = [args[1], args[2:end]..., freearg] - reorder_parameters!(cargs, 2) - return Expr(:->, freearg, Expr(:call, cargs...)) + callex = only(args) + @assert Meta.isexpr(callex, :call) + args = callex.args + func = headsym == Symbol("/>") ? + :(JuliaSyntax.fixbutfirst) : + :(JuliaSyntax.fixbutlast) + if length(args) >= 2 && Meta.isexpr(args[2], :parameters) + return Expr(:call, func, args[2], args[1], args[3:end]...) + else + return Expr(:call, func, args...) + end elseif headsym == :chain - return Expr(:call, :(JuliaSyntax.chain), args...) + if kind(node_args[1]) in KSet"/> \>" + return Expr(:call, :(JuliaSyntax.compose_chain), args...) + else + return Expr(:call, :(JuliaSyntax.chain), args...) + end end return Expr(headsym, args...) end +#------------------------------------------------------------------------------- +# Targets for lowering /> and \> syntax + +# For use with /> +struct FixButFirst{F,Args,Kws} + f::F + args::Args + kwargs::Kws +end + +(f::FixButFirst)(x) = f.f(x, f.args...; f.kwargs...) + +""" +Fix all arguments except for the first +""" +fixbutfirst(f, args...; kws...) = FixButFirst(f, args, kws) + +# For use with \> +struct FixButLast{F,Args,Kws} + f::F + args::Args + kwargs::Kws +end + +(f::FixButLast)(x) = f.f(f.args..., x; f.kwargs...) + +""" +Fix all arguments except for the last +""" +fixbutlast(f, args...; kws...) = FixButLast(f, args, kws) + chain(x, f, fs...) = chain(f(x), fs...) chain(x) = x +struct ComposeChain{Funcs} + fs::Funcs +end + +(f::ComposeChain)(x) = chain(x, f.fs...) + +compose_chain(fs...) = ComposeChain(fs) + + +#------------------------------------------------------------------------------- + Base.Expr(node::SyntaxNode) = _to_expr(node) function build_tree(::Type{Expr}, stream::ParseStream; kws...) diff --git a/src/parser.jl b/src/parser.jl index b2256e36..e6e4be87 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -827,20 +827,22 @@ end # x /> A.f(y) ==> (chain x (/> (. A (quote f)) y)) function parse_curry_chain(ps::ParseState) mark = position(ps) - parse_range(ps) - has_chain = false + nterms = 0 + if (k = peek(ps); k != K"/>" && k != K"\>") + parse_range(ps) + nterms += 1 + end while (k = peek(ps); k == K"/>" || k == K"\>") bump(ps, TRIVIA_FLAG) m = position(ps) parse_range(ps) - if peek_behind(ps).kind == K"call" - has_chain = true - reset_node!(ps, position(ps), kind=k) - else + nterms += 1 + if peek_behind(ps).kind != K"call" emit(ps, m, K"error", error="Expected call to the right of />") end + emit(ps, m, k) end - if has_chain + if nterms > 1 emit(ps, mark, K"chain") end end From cc897a05016370b8c2292512814850640cf5cebf Mon Sep 17 00:00:00 2001 From: c42f Date: Thu, 10 Nov 2022 11:37:21 +1000 Subject: [PATCH 3/8] Make chain() rewrite `\> map \> reduce` into `mapreduce` This provides an example of how such pipelines can be fundamentally more efficient than normal piping syntax. --- src/expr.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/expr.jl b/src/expr.jl index fbfcdcaa..b423ee29 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -381,6 +381,12 @@ fixbutlast(f, args...; kws...) = FixButLast(f, args, kws) chain(x, f, fs...) = chain(f(x), fs...) chain(x) = x +# An example of how chain() can be used to rewrite +# `x \> map(f) \> reduce(g)` into `mapreduce(f, g, x)` +function chain(x, f1::FixButLast{typeof(map)}, f2::FixButLast{typeof(reduce)}, fs...) + chain(x, fixbutlast(mapreduce, f1.args..., f2.args...; f1.kwargs..., f2.kwargs...), fs...) +end + struct ComposeChain{Funcs} fs::Funcs end From 92994ea1bd85b514210114688ff218e7260b9184 Mon Sep 17 00:00:00 2001 From: c42f Date: Thu, 10 Nov 2022 13:49:08 +1000 Subject: [PATCH 4/8] Tests + fix trivia attachment + allow interpolation --- src/parser.jl | 15 +++++++++++---- test/parser.jl | 10 ++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/parser.jl b/src/parser.jl index e6e4be87..eb6c4925 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -823,26 +823,33 @@ function parse_pipe_gt(ps::ParseState) parse_LtoR(ps, parse_curry_chain, is_prec_pipe_gt) end -# x /> f(y) /> g(z) ==> (chain x (/> f y) (/> g z)) -# x /> A.f(y) ==> (chain x (/> (. A (quote f)) y)) function parse_curry_chain(ps::ParseState) mark = position(ps) nterms = 0 if (k = peek(ps); k != K"/>" && k != K"\>") + # x /> f(a) ==> (chain x (/> (call f a))) parse_range(ps) nterms += 1 + else + # /> f(a) ==> (/> (call f a)) end while (k = peek(ps); k == K"/>" || k == K"\>") - bump(ps, TRIVIA_FLAG) m = position(ps) + bump(ps, TRIVIA_FLAG) parse_range(ps) nterms += 1 - if peek_behind(ps).kind != K"call" + if (kb = peek_behind(ps).kind; kb != K"call" && kb != K"$") emit(ps, m, K"error", error="Expected call to the right of />") end emit(ps, m, k) end if nterms > 1 + # x /> f(a) /> g(b) ==> (chain x (/> (call f a)) (/> (call g b))) + # x /> A.f(a,b) ==> (chain x (/> (call (. A (quote f)) a b))) + # /> f(a) /> g(b) ==> (chain (/> (call f a)) (/> (call g b))) + # x /> f() \> g() ==> (chain x (/> (call f)) (\> (call g))) + # x /> $call ==> (chain x (/> ($ call))) + # x /> notcall[] ==> (chain x (/> (error (ref notcall)))) emit(ps, mark, K"chain") end end diff --git a/test/parser.jl b/test/parser.jl index f95f176e..2067c93d 100644 --- a/test/parser.jl +++ b/test/parser.jl @@ -131,6 +131,16 @@ tests = [ "x |> y |> z" => "(call-i (call-i x |> y) |> z)" "x .|> y" => "(dotcall-i x |> y)" ], + JuliaSyntax.parse_curry_chain => [ + "x /> f(a)" => "(chain x (/> (call f a)))" + "/> f(a)" => "(/> (call f a))" + "x /> f(a) /> g(b)" => "(chain x (/> (call f a)) (/> (call g b)))" + "x /> A.f(a,b)" => "(chain x (/> (call (. A (quote f)) a b)))" + "/> f(a) /> g(b)" => "(chain (/> (call f a)) (/> (call g b)))" + "x /> f() \\> g()" => "(chain x (/> (call f)) (\\> (call g)))" + "x /> \$call" => "(chain x (/> (\$ call)))" + "x /> notcall[]" => "(chain x (/> (error (ref notcall))))" + ], JuliaSyntax.parse_range => [ "1:2" => "(call-i 1 : 2)" "1:2:3" => "(call-i 1 : 2 3)" From 44176e164f174a3d9566b179f6e9474081ae1629 Mon Sep 17 00:00:00 2001 From: c42f Date: Fri, 17 Feb 2023 10:01:52 +1000 Subject: [PATCH 5/8] Prototype: Underscores and prefix anonymous function syntax As discussed in https://github.com/JuliaLang/julia/issues/38713 --- src/expr.jl | 35 +++++++++++++++++++++++++++++++---- src/parser.jl | 9 ++++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/expr.jl b/src/expr.jl index b423ee29..10ac5d1b 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -46,6 +46,27 @@ function reorder_parameters!(args, params_pos) insert!(args, params_pos, pop!(args)) end +function lower_underscores!(anon_args, args, argrange=1:length(args)) + for i in argrange + a = args[i] + if a == :_ + g = gensym() + push!(anon_args, g) + args[i] = g + elseif a isa Expr + if Meta.isexpr(a, :call) && length(a.args) > 2 && + Meta.isexpr(a.args[2], :parameters) + lower_underscores!(anon_args, a.args, 1:1) + lower_underscores!(anon_args, a.args, 3:length(a.args)) + lower_underscores!(anon_args, a.args, 2:2) + else + # FIXME: Other out-of-source-order Exprs + lower_underscores!(anon_args, a.args) + end + end + end +end + function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, eq_to_kw=false, map_kw_in_params=false) if !haschildren(node) @@ -275,11 +296,17 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, # Block for conditional's source location args[1] = Expr(:block, loc, args[1]) elseif headsym === :(->) - if Meta.isexpr(args[2], :block) - pushfirst!(args[2].args, loc) + if is_prefix_op_call(node) + anon_args = Symbol[] + lower_underscores!(anon_args, args) + pushfirst!(args, Expr(:tuple, anon_args...)) else - # Add block for source locations - args[2] = Expr(:block, loc, args[2]) + if Meta.isexpr(args[2], :block) + pushfirst!(args[2].args, loc) + else + # Add block for source locations + args[2] = Expr(:block, loc, args[2]) + end end elseif headsym === :function if length(args) > 1 diff --git a/src/parser.jl b/src/parser.jl index eb6c4925..f539220a 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -276,7 +276,7 @@ function is_syntactic_operator(k) end function is_syntactic_unary_op(k) - kind(k) in KSet"$ & ::" + kind(k) in KSet"$ & :: ->" end function is_type_operator(t) @@ -1448,6 +1448,7 @@ end # &a ==> (& a) # ::a ==> (::-pre a) # $a ==> ($ a) +# ->a ==> (-> a) # # flisp: parse-unary-prefix function parse_unary_prefix(ps::ParseState) @@ -1465,14 +1466,16 @@ function parse_unary_prefix(ps::ParseState) if k in KSet"& ::" # &a ==> (& a) parse_where(ps, parse_call) + elseif k == K"->" + # -> binds loosely on the right + parse_eq_star(ps) else # $a ==> ($ a) # $$a ==> ($ ($ a)) # $&a ==> ($ (& a)) parse_unary_prefix(ps) end - # Only need PREFIX_OP_FLAG for :: - f = k == K"::" ? PREFIX_OP_FLAG : EMPTY_FLAGS + f = (k == K"::" || k == K"->") ? PREFIX_OP_FLAG : EMPTY_FLAGS emit(ps, mark, k, f) end else From 46e3076d60077a145a61648045b03bc03032d7cc Mon Sep 17 00:00:00 2001 From: c42f Date: Sat, 18 Feb 2023 11:04:48 +1000 Subject: [PATCH 6/8] combine with underscore lowering from #199 --- src/expr.jl | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/expr.jl b/src/expr.jl index 10ac5d1b..26c7e30e 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -11,22 +11,6 @@ function is_stringchunk(node) return k == K"String" || k == K"CmdString" end -function lower_underscores(args, skiparg=-1) - g = nothing - for i in 1:length(args) - if i == skiparg - continue - end - if args[i] == :_ - if isnothing(g) - g = gensym() - end - args[i] = g - end - end - return g -end - function reorder_parameters!(args, params_pos) p = 0 for i = length(args):-1:1 @@ -206,7 +190,6 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, headsym = Symbol("'") end # Move parameters blocks to args[2] - g = lower_underscores(args, 1) reorder_parameters!(args, 2) if headsym === :dotcall if is_prefix_call(node) @@ -217,17 +200,9 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, args[1] = Symbol(".", args[1]) end end - if !isnothing(g) - return Expr(:->, g, Expr(:call, args...)) - end elseif headsym in (:ref, :curly) # Move parameters blocks to args[2] reorder_parameters!(args, 2) - elseif headsym == :. - g = lower_underscores(args) - if !isnothing(g) - return Expr(:->, g, Expr(:., args...)) - end elseif headsym in (:tuple, :vect, :braces) # Move parameters blocks to args[1] reorder_parameters!(args, 1) @@ -352,13 +327,37 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, headsym = :const end elseif headsym == Symbol("/>") || headsym == Symbol("\\>") - freearg = gensym() callex = only(args) @assert Meta.isexpr(callex, :call) args = callex.args func = headsym == Symbol("/>") ? :(JuliaSyntax.fixbutfirst) : :(JuliaSyntax.fixbutlast) + + # Automatic underscore lowering within pipes + for i = 2:length(args) + anon_args = Symbol[] + if i == 2 && Meta.isexpr(args[i], :parameters) + kws = args[i].args + for j = 1:length(kws) + kw = kws[j] + if Meta.isexpr(kw, :kw) + as = Any[kw.args[2]] + lower_underscores!(anon_args, as) + if !isempty(anon_args) + kw.args[2] = Expr(:->, Expr(:tuple, anon_args...), as[1]) + end + end + end + else + as = Any[args[i]] + lower_underscores!(anon_args, as) + if !isempty(anon_args) + args[i] = Expr(:->, Expr(:tuple, anon_args...), as[1]) + end + end + end + if length(args) >= 2 && Meta.isexpr(args[2], :parameters) return Expr(:call, func, args[2], args[1], args[3:end]...) else From 89054a806e2b8f28c0546eef11f9204a60e0ef2f Mon Sep 17 00:00:00 2001 From: c42f Date: Sat, 18 Feb 2023 11:07:38 +1000 Subject: [PATCH 7/8] Replace `\>` with `/>>` This makes it visually clear which argument is being piped into --- src/expr.jl | 10 +++++----- src/kinds.jl | 2 +- src/parser.jl | 6 +++--- src/tokenize.jl | 8 +++++--- test/parser.jl | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/expr.jl b/src/expr.jl index 26c7e30e..0af323d1 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -326,7 +326,7 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, args[1] = Expr(headsym, args[1].args...) headsym = :const end - elseif headsym == Symbol("/>") || headsym == Symbol("\\>") + elseif headsym == Symbol("/>") || headsym == Symbol("/>>") callex = only(args) @assert Meta.isexpr(callex, :call) args = callex.args @@ -364,7 +364,7 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, return Expr(:call, func, args...) end elseif headsym == :chain - if kind(node_args[1]) in KSet"/> \>" + if kind(node_args[1]) in KSet"/> />>" return Expr(:call, :(JuliaSyntax.compose_chain), args...) else return Expr(:call, :(JuliaSyntax.chain), args...) @@ -374,7 +374,7 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, end #------------------------------------------------------------------------------- -# Targets for lowering /> and \> syntax +# Targets for lowering /> and />> syntax # For use with /> struct FixButFirst{F,Args,Kws} @@ -390,7 +390,7 @@ Fix all arguments except for the first """ fixbutfirst(f, args...; kws...) = FixButFirst(f, args, kws) -# For use with \> +# For use with />> struct FixButLast{F,Args,Kws} f::F args::Args @@ -408,7 +408,7 @@ chain(x, f, fs...) = chain(f(x), fs...) chain(x) = x # An example of how chain() can be used to rewrite -# `x \> map(f) \> reduce(g)` into `mapreduce(f, g, x)` +# `x />> map(f) />> reduce(g)` into `mapreduce(f, g, x)` function chain(x, f1::FixButLast{typeof(map)}, f2::FixButLast{typeof(reduce)}, fs...) chain(x, fixbutlast(mapreduce, f1.args..., f2.args...; f1.kwargs..., f2.kwargs...), fs...) end diff --git a/src/kinds.jl b/src/kinds.jl index 95a7b689..de8ddb41 100644 --- a/src/kinds.jl +++ b/src/kinds.jl @@ -824,7 +824,7 @@ const _kind_names = ".'" "->" "/>" - "\\>" + "/>>" "BEGIN_UNICODE_OPS" "¬" diff --git a/src/parser.jl b/src/parser.jl index f539220a..284dcfa2 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -826,14 +826,14 @@ end function parse_curry_chain(ps::ParseState) mark = position(ps) nterms = 0 - if (k = peek(ps); k != K"/>" && k != K"\>") + if (k = peek(ps); k != K"/>" && k != K"/>>") # x /> f(a) ==> (chain x (/> (call f a))) parse_range(ps) nterms += 1 else # /> f(a) ==> (/> (call f a)) end - while (k = peek(ps); k == K"/>" || k == K"\>") + while (k = peek(ps); k == K"/>" || k == K"/>>") m = position(ps) bump(ps, TRIVIA_FLAG) parse_range(ps) @@ -847,7 +847,7 @@ function parse_curry_chain(ps::ParseState) # x /> f(a) /> g(b) ==> (chain x (/> (call f a)) (/> (call g b))) # x /> A.f(a,b) ==> (chain x (/> (call (. A (quote f)) a b))) # /> f(a) /> g(b) ==> (chain (/> (call f a)) (/> (call g b))) - # x /> f() \> g() ==> (chain x (/> (call f)) (\> (call g))) + # x /> f() />> g() ==> (chain x (/> (call f)) (/>> (call g))) # x /> $call ==> (chain x (/> ($ call))) # x /> notcall[] ==> (chain x (/> (error (ref notcall)))) emit(ps, mark, K"chain") diff --git a/src/tokenize.jl b/src/tokenize.jl index c4319726..764b4b25 100644 --- a/src/tokenize.jl +++ b/src/tokenize.jl @@ -933,7 +933,11 @@ function lex_forwardslash(l::Lexer) elseif accept(l, '=') return emit(l, K"/=") elseif accept(l, '>') - return emit(l, K"/>") + if accept(l, '>') + return emit(l, K"/>>") + else + return emit(l, K"/>") + end else return emit(l, K"/") end @@ -942,8 +946,6 @@ end function lex_backslash(l::Lexer) if accept(l, '=') return emit(l, K"\=") - elseif accept(l, '>') - return emit(l, K"\>") end return emit(l, K"\\") end diff --git a/test/parser.jl b/test/parser.jl index 2067c93d..b930bb57 100644 --- a/test/parser.jl +++ b/test/parser.jl @@ -137,7 +137,7 @@ tests = [ "x /> f(a) /> g(b)" => "(chain x (/> (call f a)) (/> (call g b)))" "x /> A.f(a,b)" => "(chain x (/> (call (. A (quote f)) a b)))" "/> f(a) /> g(b)" => "(chain (/> (call f a)) (/> (call g b)))" - "x /> f() \\> g()" => "(chain x (/> (call f)) (\\> (call g)))" + "x /> f() />> g()" => "(chain x (/> (call f)) (/>> (call g)))" "x /> \$call" => "(chain x (/> (\$ call)))" "x /> notcall[]" => "(chain x (/> (error (ref notcall))))" ], From b02929731772a46f8022dbd385e648e4d921bd90 Mon Sep 17 00:00:00 2001 From: c42f Date: Sat, 18 Feb 2023 14:03:03 +1000 Subject: [PATCH 8/8] Make _ always refer to the same (single) argument --- src/expr.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/expr.jl b/src/expr.jl index 0af323d1..3fc745c0 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -34,8 +34,12 @@ function lower_underscores!(anon_args, args, argrange=1:length(args)) for i in argrange a = args[i] if a == :_ - g = gensym() - push!(anon_args, g) + if isempty(anon_args) + g = gensym() + push!(anon_args, g) + else + g = anon_args[1] + end args[i] = g elseif a isa Expr if Meta.isexpr(a, :call) && length(a.args) > 2 &&