From 942c984ed0a79ba59f18a7e99f09b565a4ac14cc Mon Sep 17 00:00:00 2001 From: c42f Date: Sun, 20 Nov 2022 21:42:13 +1000 Subject: [PATCH] Parse dotted calls with `dotcall` head Dotted call syntax parses into various forms which aren't really consistent. Especially, Expr is inconsistent about dotted infix calls vs dotted prefix calls. In this change we adopt a more consistent (and hopefully less mysterious!) parsing where dotted calls get their own `dotcall` head which is otherwise like the `call` head: f.(a, b) ==> (dotcall f a b) a .+ b ==> (dotcall-i a + b) .+ b ==> (dotcall-pre + b) .+(b) ==> (dotcall-pre + b) Also, in comparison chains where a dotted operator appears as an atom we split the dot from an operator, so `.+` becomes `(. +)`: a .< b < c ==> (comparison a (. <) b < c) There's other cases where it would also be consistent to split the dot from the operator, but these are more challenging to convert back to a compatible Expr so I've punted on these for now. For example, we'd like .*(a,b) ==> (call (. *) a b) .+(a,) ==> (call (. +) a) but these are not yet implemented as we need to be able to distinguish them from the likes of `(.+)(a,)` which the reference parser treats differently from `.+(a,)` --- src/expr.jl | 26 +++-- src/kinds.jl | 1 + src/parse_stream.jl | 6 +- src/parser.jl | 226 +++++++++++++++++++++++++++----------------- test/expr.jl | 12 +++ test/parser.jl | 55 +++++++---- 6 files changed, 213 insertions(+), 113 deletions(-) diff --git a/src/expr.jl b/src/expr.jl index f7caadbc..04f1a979 100644 --- a/src/expr.jl +++ b/src/expr.jl @@ -31,7 +31,7 @@ function reorder_parameters!(args, params_pos) end function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, - eq_to_kw=false, inside_dot_expr=false, inside_vect_or_braces=false) + eq_to_kw=false, inside_vect_or_braces=false) if !haschildren(node) val = node.val if val isa Union{Int128,UInt128,BigInt} @@ -125,11 +125,9 @@ 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 && is_prefix_call(node) || + ((headsym == :call || headsym == :dotcall) && is_prefix_call(node)) || headsym == :ref - eq_to_kw_all = headsym == :parameters && !inside_vect_or_braces || - (headsym == :tuple && inside_dot_expr) - in_dot = headsym == :. + eq_to_kw_all = headsym == :parameters && !inside_vect_or_braces in_vb = headsym == :vect || headsym == :braces if insert_linenums && isempty(node_args) push!(args, source_location(LineNumberNode, node.source, node.position)) @@ -142,7 +140,6 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, eq_to_kw = eq_to_kw_in_call && i > 1 || eq_to_kw_all args[insert_linenums ? 2*i : i] = _to_expr(n, eq_to_kw=eq_to_kw, - inside_dot_expr=in_dot, inside_vect_or_braces=in_vb) end end @@ -155,7 +152,7 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, if args[1] == Symbol("@.") args[1] = Symbol("@__dot__") end - elseif headsym in (:call, :ref) + elseif headsym in (:dotcall, :call, :ref) # Julia's standard `Expr` ASTs have children stored in a canonical # order which is often not always source order. We permute the children # here as necessary to get the canonical order. @@ -169,6 +166,21 @@ function _to_expr(node::SyntaxNode; iteration_spec=false, need_linenodes=true, end # Move parameters blocks to args[2] reorder_parameters!(args, 2) + if headsym === :dotcall + if is_prefix_call(node) + return Expr(:., args[1], Expr(:tuple, args[2:end]...)) + else + # operator calls + headsym = :call + args[1] = Symbol(".", args[1]) + end + end + elseif headsym === :comparison + for i in 1:length(args) + if Meta.isexpr(args[i], :., 1) + args[i] = Symbol(".",args[i].args[1]) + end + end elseif headsym in (:tuple, :vect, :braces) # Move parameters blocks to args[1] reorder_parameters!(args, 1) diff --git a/src/kinds.jl b/src/kinds.jl index 2ceabb3e..9fded1d4 100644 --- a/src/kinds.jl +++ b/src/kinds.jl @@ -868,6 +868,7 @@ const _kind_names = "BEGIN_SYNTAX_KINDS" "block" "call" + "dotcall" "comparison" "curly" "inert" # QuoteNode; not quasiquote diff --git a/src/parse_stream.jl b/src/parse_stream.jl index 5fedfbfd..e073d90f 100644 --- a/src/parse_stream.jl +++ b/src/parse_stream.jl @@ -83,7 +83,7 @@ function untokenize(head::SyntaxHead; unique=true, include_flag_suff=true) if include_flag_suff && suffix_flags != EMPTY_FLAGS str = str*"-" is_trivia(head) && (str = str*"t") - is_infix_op_call(head) && (str = str*"i") + is_infix_op_call(head) && (str = str*"i") is_prefix_op_call(head) && (str = str*"pre") is_postfix_op_call(head) && (str = str*"post") has_flags(head, TRIPLE_STRING_FLAG) && (str = str*"s") @@ -725,9 +725,7 @@ function bump_split(stream::ParseStream, split_spec...) push!(stream.tokens, SyntaxToken(h, kind(tok), b)) end stream.peek_count = 0 - # Returning position(stream) like the other bump* methods would be - # ambiguous here; return nothing instead. - nothing + return position(stream) end function _reset_node_head(x, k, f) diff --git a/src/parser.jl b/src/parser.jl index 9e565225..bc3877e7 100644 --- a/src/parser.jl +++ b/src/parser.jl @@ -297,17 +297,6 @@ function is_both_unary_and_binary(t) ) end -# operators handled by parse_unary at the start of an expression -function is_initial_operator(t) - k = kind(t) - # TODO(jb): `?` should probably not be listed here except for the syntax hack in osutils.jl - is_operator(k) && - !is_word_operator(k) && - !(k in KSet": ' .' ?") && - !(is_syntactic_unary_op(k) && !is_dotted(t)) && - !is_syntactic_operator(k) -end - # flisp: invalid-identifier? function is_valid_identifier(k) k = kind(k) @@ -330,6 +319,27 @@ function was_eventually_call(ps::ParseState) end end +function bump_dotsplit(ps, flags=EMPTY_FLAGS; + emit_dot_node::Bool=false, remap_kind::Kind=K"None") + t = peek_token(ps) + if is_dotted(t) + bump_trivia(ps) + mark = position(ps) + k = remap_kind != K"None" ? remap_kind : kind(t) + pos = bump_split(ps, (1, K".", TRIVIA_FLAG), (0, k, flags)) + if emit_dot_node + pos = emit(ps, mark, K".") + end + else + if remap_kind != K"None" + pos = bump(ps, remap_kind=remap_kind) + else + pos = bump(ps) + end + end + return pos +end + #------------------------------------------------------------------------------- # Parser # @@ -351,9 +361,10 @@ function parse_LtoR(ps::ParseState, down, is_op) mark = position(ps) down(ps) while is_op(peek(ps)) - bump(ps) + t = peek_token(ps) + bump_dotsplit(ps) down(ps) - emit(ps, mark, K"call", INFIX_FLAG) + emit(ps, mark, is_dotted(t) ? K"dotcall" : K"call", INFIX_FLAG) end end @@ -364,11 +375,11 @@ end function parse_RtoL(ps::ParseState, down, is_op, self) mark = position(ps) down(ps) - k = peek(ps) - if is_op(k) - bump(ps) + t = peek_token(ps) + if is_op(kind(t)) + bump_dotsplit(ps) self(ps) - emit(ps, mark, K"call", INFIX_FLAG) + emit(ps, mark, is_dotted(t) ? K"dotcall" : K"call", INFIX_FLAG) end end @@ -573,11 +584,12 @@ function parse_assignment_with_initial_ex(ps::ParseState, mark, down::T) where { end # ~ is the only non-syntactic assignment-precedence operator. # a ~ b ==> (call-i a ~ b) + # a .~ b ==> (dotcall-i a ~ b) # [a ~ b c] ==> (hcat (call-i a ~ b) c) # [a~b] ==> (vect (call-i a ~ b)) - bump(ps) + bump_dotsplit(ps) parse_assignment(ps, down) - emit(ps, mark, K"call", INFIX_FLAG) + emit(ps, mark, is_dotted(t) ? K"dotcall" : K"call", INFIX_FLAG) else # a += b ==> (+= a b) # a .= b ==> (.= a b) @@ -614,6 +626,7 @@ end # flisp: parse-pair # a => b ==> (call-i a => b) +# a .=> b ==> (dotcall-i a => b) function parse_pair(ps::ParseState) parse_RtoL(ps, parse_cond, is_prec_pair, parse_pair) end @@ -698,11 +711,11 @@ function parse_arrow(ps::ParseState) else # x → y ==> (call-i x → y) # x <--> y ==> (call-i x <--> y) - # x .--> y ==> (call-i x .--> y) + # x .--> y ==> (dotcall-i x --> y) # x -->₁ y ==> (call-i x -->₁ y) - bump(ps) + bump_dotsplit(ps) parse_arrow(ps) - emit(ps, mark, K"call", INFIX_FLAG) + emit(ps, mark, is_dotted(t) ? K"dotcall" : K"call", INFIX_FLAG) end end end @@ -757,10 +770,12 @@ function parse_comparison(ps::ParseState, subtype_comparison=false) end n_comparisons = 0 op_pos = NO_POSITION + op_dotted = false initial_tok = peek_token(ps) - while is_prec_comparison(peek(ps)) + while (t = peek_token(ps); is_prec_comparison(t)) n_comparisons += 1 - op_pos = bump(ps) + op_dotted = is_dotted(t) + op_pos = bump_dotsplit(ps, emit_dot_node=true) parse_pipe_lt(ps) end if n_comparisons == 1 @@ -773,13 +788,19 @@ function parse_comparison(ps::ParseState, subtype_comparison=false) else # Normal binary comparisons # x < y ==> (call-i x < y) - # x .<: y ==> (call-i x .<: y) - emit(ps, mark, K"call", INFIX_FLAG) + # x .< y ==> (dotcall-i x < y) + if op_dotted + # x .<: y ==> (dotcall-i x <: y) + reset_node!(ps, op_pos, kind=K"TOMBSTONE", flags=TRIVIA_FLAG) + end + emit(ps, mark, is_dotted(initial_tok) ? K"dotcall" : K"call", INFIX_FLAG) end elseif n_comparisons > 1 # Comparison chains # x < y < z ==> (comparison x < y < z) # x == y < z ==> (comparison x == y < z) + # x .< y .< z ==> (comparison x (. <) y (. <) z) + # x .< y < z ==> (comparison x (. <) y < z) emit(ps, mark, K"comparison") end end @@ -791,6 +812,7 @@ function parse_pipe_lt(ps::ParseState) end # x |> y |> z ==> (call-i (call-i x |> y) |> z) +# x .|> y ==> (dotcall-i x |> y) # flisp: parse-pipe> function parse_pipe_gt(ps::ParseState) parse_LtoR(ps, parse_range, is_prec_pipe_gt) @@ -807,15 +829,17 @@ end function parse_range(ps::ParseState) mark = position(ps) parse_expr(ps) - initial_kind = peek(ps) + initial_tok = peek_token(ps) + initial_kind = kind(initial_tok) if initial_kind != K":" && is_prec_colon(initial_kind) # a..b ==> (call-i a .. b) # a … b ==> (call-i a … b) - bump(ps) + # a .… b ==> (dotcall-i a … b) + bump_dotsplit(ps) parse_expr(ps) - emit(ps, mark, K"call", INFIX_FLAG) + emit(ps, mark, is_dotted(initial_tok) ? K"dotcall" : K"call", INFIX_FLAG) elseif initial_kind == K":" && ps.range_colon_enabled - # a ? b : c:d ==> (if a b (call-i c : d)) + # a ? b : c:d ==> (? a b (call-i c : d)) n_colons = 0 while peek(ps) == K":" if ps.space_sensitive && @@ -888,6 +912,7 @@ end # a - b - c ==> (call-i (call-i a - b) - c) # a + b + c ==> (call-i a + b c) +# a .+ b ==> (dotcall-i a + b) # # flisp: parse-expr function parse_expr(ps::ParseState) @@ -920,16 +945,16 @@ function parse_with_chains(ps::ParseState, down, is_op, chain_ops) # [x+y + z] ==> (vect (call-i x + y z)) break end - bump(ps) + bump_dotsplit(ps) down(ps) if kind(t) in chain_ops && !is_decorated(t) # a + b + c ==> (call-i a + b c) - # a + b .+ c ==> (call-i (call-i a + b) .+ c) + # a + b .+ c ==> (dotcall-i (call-i a + b) + c) parse_chain(ps, down, kind(t)) end # a +₁ b +₁ c ==> (call-i (call-i a +₁ b) +₁ c) - # a .+ b .+ c ==> (call-i (call-i a .+ b) .+ c) - emit(ps, mark, K"call", INFIX_FLAG) + # a .+ b .+ c ==> (dotcall-i (dotcall-i a + b) + c) + emit(ps, mark, is_dotted(t) ? K"dotcall" : K"call", INFIX_FLAG) end end @@ -950,11 +975,13 @@ function parse_chain(ps::ParseState, down, op_kind) end # flisp: parse-rational +# x // y // z ==> (call-i (call-i x // y) // z) function parse_rational(ps::ParseState) parse_LtoR(ps, parse_shift, is_prec_rational) end # flisp: parse-shift +# x >> y >> z ==> (call-i (call-i x >> y) >> z) function parse_shift(ps::ParseState) parse_LtoR(ps, parse_unary_subtype, is_prec_bitshift) end @@ -963,8 +990,8 @@ end # # flisp: parse-unary-subtype function parse_unary_subtype(ps::ParseState) - k = peek(ps, skip_newlines=true) - if k in KSet"<: >:" + t = peek_token(ps, skip_newlines=true) + if is_type_operator(t) k2 = peek(ps, 2) if is_closing_token(ps, k2) || k2 in KSet"NewlineWs =" # return operator by itself @@ -978,13 +1005,14 @@ function parse_unary_subtype(ps::ParseState) # <:(x::T) ==> (<:-pre (:: x T)) parse_where(ps, parse_juxtapose) else + # <: x ==> (<:-pre x) # <: A where B ==> (<:-pre (where A B)) mark = position(ps) bump(ps, TRIVIA_FLAG) parse_where(ps, parse_juxtapose) # Flisp parser handled this, but I don't know how it can happen... @check peek_behind(ps).kind != K"tuple" - emit(ps, mark, k, PREFIX_OP_FLAG) + emit(ps, mark, kind(t), PREFIX_OP_FLAG) end else parse_where(ps, parse_juxtapose) @@ -1103,22 +1131,33 @@ function parse_juxtapose(ps::ParseState) end end -# Deal with numeric literal prefixes and unary calls +# Parse numeric literal prefixes, calls to unary operators and prefix +# calls involving arbitrary operators with bracketed arglists (as opposed to +# infix notation) # -# flisp: parse-unary +# flisp: parse-unary, parse-unary-call function parse_unary(ps::ParseState) mark = position(ps) bump_trivia(ps) - t = peek_token(ps) - k = kind(t) - if !is_initial_operator(t) + op_t = peek_token(ps) + op_k = kind(op_t) + if ( + !is_operator(op_k) || + is_word_operator(op_k) || + # TODO(jb): `?` should probably not be listed here + # except for the syntax hack in osutils.jl + (op_k in KSet": ' .' ?") || + (is_syntactic_unary_op(op_k) && !is_dotted(op_t)) || + is_syntactic_operator(op_k) + ) + # `op_t` is not an initial operator # :T ==> (quote T) # in::T ==> (:: in T) # isa::T ==> (:: isa T) parse_factor(ps) return end - if k in KSet"- +" && !is_decorated(t) + if op_k in KSet"- +" && !is_decorated(op_t) t2 = peek_token(ps, 2) if !preceding_whitespace(t2) && kind(t2) in KSet"Integer Float Float32" k3 = peek(ps, 3) @@ -1142,26 +1181,6 @@ function parse_unary(ps::ParseState) return end end - # Things which are not quite negative literals result in a unary call instead - # -0x1 ==> (call-pre - 0x01) - # - 2 ==> (call-pre - 2) - # .-2 ==> (call-pre .- 2) - parse_unary_call(ps) -end - -# Parse calls to unary operators and prefix calls involving arbitrary operators -# with bracketed arglists (as opposed to infix notation) -# -# +a ==> (call-pre + a) -# +(a,b) ==> (call-pre + a b) -# -# flisp: parse-unary-call -function parse_unary_call(ps::ParseState) - mark = position(ps) - op_t = peek_token(ps) - op_k = kind(op_t) - op_node_kind = is_type_operator(op_t) ? op_k : K"call" - op_tok_flags = is_type_operator(op_t) ? TRIVIA_FLAG : EMPTY_FLAGS t2 = peek_token(ps, 2) k2 = kind(t2) if is_closing_token(ps, k2) || k2 in KSet"NewlineWs =" @@ -1172,9 +1191,7 @@ function parse_unary_call(ps::ParseState) # .+ = ==> (. +) # .+) ==> (. +) # .& ==> (. &) - bump_trivia(ps) - bump_split(ps, (1, K".", TRIVIA_FLAG), (0, op_k, EMPTY_FLAGS)) - emit(ps, mark, K".") + bump_dotsplit(ps, emit_dot_node=true) else # Standalone non-dotted operators # +) ==> + @@ -1184,6 +1201,7 @@ function parse_unary_call(ps::ParseState) # Call with type parameters or non-unary prefix call # +{T}(x::T) ==> (call (curly + T) (:: x T)) # *(x) ==> (call * x) + # .*(x) ==> (call .* x) parse_factor(ps) elseif k2 == K"(" # Cases like +(a;b) are ambiguous: are they prefix calls to + with b as @@ -1193,7 +1211,7 @@ function parse_unary_call(ps::ParseState) # # (The flisp parser only considers commas before `;` and thus gets this # last case wrong) - bump(ps, op_tok_flags) + op_pos = bump_dotsplit(ps, emit_dot_node=true) # Setup possible whitespace error between operator and ( ws_mark = position(ps) @@ -1205,15 +1223,15 @@ function parse_unary_call(ps::ParseState) bump(ps, TRIVIA_FLAG) # ( initial_semi = peek(ps) == K";" opts = parse_brackets(ps, K")") do had_commas, had_splat, num_semis, num_subexprs - is_call = had_commas || had_splat || initial_semi - return (needs_parameters=is_call, - is_call=is_call, - is_block=!is_call && num_semis > 0) + is_paren_call = had_commas || had_splat || initial_semi + return (needs_parameters=is_paren_call, + is_paren_call=is_paren_call, + is_block=!is_paren_call && num_semis > 0) end # The precedence between unary + and any following infix ^ depends on # whether the parens are a function call or not - if opts.is_call + if opts.is_paren_call if preceding_whitespace(t2) # Whitespace not allowed before prefix function call bracket # + (a,b) ==> (call + (error) a b) @@ -1230,11 +1248,31 @@ function parse_unary_call(ps::ParseState) # Prefix calls have higher precedence than ^ # +(a,b)^2 ==> (call-i (call + a b) ^ 2) # +(a,b)(x)^2 ==> (call-i (call (call + a b) x) ^ 2) - emit(ps, mark, op_node_kind) + if is_type_operator(op_t) + # <:(a,) ==> (<: a) + emit(ps, mark, op_k) + reset_node!(ps, op_pos, flags=TRIVIA_FLAG) + else + if is_dotted(op_t) + # Ugly hack to undo the split in bump_dotsplit + # .+(a,) ==> (call .+ a) + reset_node!(ps, op_pos, kind=K"TOMBSTONE") + tb1 = ps.stream.tokens[op_pos.token_index-1] + ps.stream.tokens[op_pos.token_index-1] = + SyntaxToken(SyntaxHead(K"TOMBSTONE", EMPTY_FLAGS), + K"TOMBSTONE", tb1.next_byte-1) + tb0 = ps.stream.tokens[op_pos.token_index] + ps.stream.tokens[op_pos.token_index] = + SyntaxToken(SyntaxHead(kind(tb0), flags(tb0)), + tb0.orig_kind, tb0.next_byte) + end + emit(ps, mark, K"call") + end parse_call_chain(ps, mark) parse_factor_with_initial_ex(ps, mark) else # Unary function calls with brackets as grouping, not an arglist + # .+(a) ==> (dotcall-pre (. +) a) if opts.is_block # +(a;b) ==> (call-pre + (block a b)) emit(ps, mark_before_paren, K"block") @@ -1243,26 +1281,43 @@ function parse_unary_call(ps::ParseState) # +(a=1) ==> (call-pre + (= a 1)) # Unary operators have lower precedence than ^ # +(a)^2 ==> (call-pre + (call-i a ^ 2)) + # .+(a)^2 ==> (dotcall-pre + (call-i a ^ 2)) # +(a)(x,y)^2 ==> (call-pre + (call-i (call a x y) ^ 2)) parse_call_chain(ps, mark_before_paren) parse_factor_with_initial_ex(ps, mark_before_paren) - emit(ps, mark, op_node_kind, PREFIX_OP_FLAG) + if is_type_operator(op_t) + # <:(a) ==> (<:-pre a) + emit(ps, mark, op_k, PREFIX_OP_FLAG) + reset_node!(ps, op_pos, flags=TRIVIA_FLAG) + else + if is_dotted(op_t) + emit(ps, mark, K"dotcall", PREFIX_OP_FLAG) + reset_node!(ps, op_pos, kind=K"TOMBSTONE") + else + emit(ps, mark, K"call", PREFIX_OP_FLAG) + end + end end else + @assert !is_type_operator(op_t) # `<:x` handled in parse_unary_subtype if is_unary_op(op_t) # Normal unary calls # +x ==> (call-pre + x) # √x ==> (call-pre √ x) - # ±x ==> (call-pre ± x) - bump(ps, op_tok_flags) + # .~x ==> (dotcall-pre ~ x) + # Things which are not quite negative literals + # -0x1 ==> (call-pre - 0x01) + # - 2 ==> (call-pre - 2) + # .-2 ==> (dotcall-pre - 2) + bump_dotsplit(ps, EMPTY_FLAGS) else # /x ==> (call-pre (error /) x) # +₁ x ==> (call-pre (error +₁) x) - # .<: x ==> (call-pre (error .<:) x) + # .<: x ==> (dotcall-pre (error .<:) x) bump(ps, error="not a unary operator") end parse_unary(ps) - emit(ps, mark, op_node_kind, PREFIX_OP_FLAG) + emit(ps, mark, is_dotted(op_t) ? K"dotcall" : K"call", PREFIX_OP_FLAG) end end @@ -1270,6 +1325,7 @@ end # # x^y ==> (call-i x ^ y) # x^y^z ==> (call-i x ^ (call-i y ^ z)) +# x .^ y ==> (dotcall-i x ^ y) # begin x end::T ==> (:: (block x) T) # # flisp: parse-factor @@ -1282,10 +1338,10 @@ end # flisp: parse-factor-with-initial-ex function parse_factor_with_initial_ex(ps::ParseState, mark) parse_decl_with_initial_ex(ps, mark) - if is_prec_power(peek(ps)) - bump(ps) + if (t = peek_token(ps); is_prec_power(kind(t))) + bump_dotsplit(ps) parse_factor_after(ps) - emit(ps, mark, K"call", INFIX_FLAG) + emit(ps, mark, is_dotted(t) ? K"dotcall" : K"call", INFIX_FLAG) end end @@ -1526,14 +1582,12 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false) emit_diagnostic(ps, mark, error="dot call syntax not supported for macros") end - # f.(a,b) ==> (. f (tuple a b)) - # f. (x) ==> (. f (error-t) (tuple x)) + # f.(a,b) ==> (dotcall f a b) + # f. (x) ==> (dotcall f (error-t) x) bump_disallowed_space(ps) - m = position(ps) bump(ps, TRIVIA_FLAG) parse_call_arglist(ps, K")") - emit(ps, m, K"tuple") - emit(ps, mark, K".") + emit(ps, mark, K"dotcall") elseif k == K":" # A.:+ ==> (. A (quote +)) # A.: + ==> (. A (error-t) (quote +)) diff --git a/test/expr.jl b/test/expr.jl index 9202c22b..0d0d99e5 100644 --- a/test/expr.jl +++ b/test/expr.jl @@ -223,4 +223,16 @@ @test parse(Expr, "f(a .= 1)") == Expr(:call, :f, Expr(:.=, :a, 1)) end + + @testset "dotcall" begin + parse(Expr, "f.(x,y)") == Expr(:., :f, Expr(:tuple, :x, :y)) + parse(Expr, "f.(x=1)") == Expr(:., :f, Expr(:tuple, Expr(:kw, :x, 1))) + parse(Expr, "x .+ y") == Expr(:call, Symbol(".+"), :x, :y) + parse(Expr, "(x=1) .+ y") == Expr(:call, Symbol(".+"), Expr(:(=), :x, 1), :y) + parse(Expr, "a .< b .< c") == Expr(:comparison, :a, Symbol(".<"), + :b, Symbol(".<"), :c) + parse(Expr, ".*(x)") == Expr(:call, Symbol(".*"), :x) + parse(Expr, ".+(x)") == Expr(:call, Symbol(".+"), :x) + parse(Expr, ".+x") == Expr(:call, Symbol(".+"), :x) + end end diff --git a/test/parser.jl b/test/parser.jl index 8ae2b668..82b3e144 100644 --- a/test/parser.jl +++ b/test/parser.jl @@ -51,11 +51,13 @@ tests = [ "x, = xs" => "(= (tuple x) xs)" "[a ~b]" => "(hcat a (call-pre ~ b))" "a ~ b" => "(call-i a ~ b)" + "a .~ b" => "(dotcall-i a ~ b)" "[a ~ b c]" => "(hcat (call-i a ~ b) c)" "[a~b]" => "(vect (call-i a ~ b))" ], JuliaSyntax.parse_pair => [ "a => b" => "(call-i a => b)" + "a .=> b" => "(dotcall-i a => b)" ], JuliaSyntax.parse_cond => [ "a ? b : c" => "(? a b c)" @@ -74,7 +76,7 @@ tests = [ "x → y" => "(call-i x → y)" "x <--> y" => "(call-i x <--> y)" "x --> y" => "(--> x y)" - "x .--> y" => "(call-i x .--> y)" + "x .--> y" => "(dotcall-i x --> y)" "x -->₁ y" => "(call-i x -->₁ y)" ], JuliaSyntax.parse_or => [ @@ -93,15 +95,20 @@ tests = [ "x >: y" => "(>: x y)" # Normal binary comparisons "x < y" => "(call-i x < y)" + "x .< y" => "(dotcall-i x < y)" + "x .<: y" => "(dotcall-i x <: y)" # Comparison chains "x < y < z" => "(comparison x < y < z)" "x == y < z" => "(comparison x == y < z)" + "x .< y .< z" => "(comparison x (. <) y (. <) z)" + "x .< y < z" => "(comparison x (. <) y < z)" ], JuliaSyntax.parse_pipe_lt => [ "x <| y <| z" => "(call-i x <| (call-i y <| z))" ], JuliaSyntax.parse_pipe_gt => [ "x |> y |> z" => "(call-i (call-i x |> y) |> z)" + "x .|> y" => "(dotcall-i x |> y)" ], JuliaSyntax.parse_range => [ "1:2" => "(call-i 1 : 2)" @@ -113,6 +120,7 @@ tests = [ JuliaSyntax.parse_range => [ "a..b" => "(call-i a .. b)" "a … b" => "(call-i a … b)" + "a .… b" => "(dotcall-i a … b)" "[1 :a]" => "(hcat 1 (quote a))" "[1 2:3 :a]" => "(hcat 1 (call-i 2 : 3) (quote a))" "x..." => "(... x)" @@ -122,7 +130,7 @@ tests = [ JuliaSyntax.parse_expr => [ "a - b - c" => "(call-i (call-i a - b) - c)" "a + b + c" => "(call-i a + b c)" - "a + b .+ c" => "(call-i (call-i a + b) .+ c)" + "a + b .+ c" => "(dotcall-i (call-i a + b) + c)" # parse_with_chains: # The following is two elements of a hcat "[x +y]" => "(hcat x (call-pre + y))" @@ -132,13 +140,20 @@ tests = [ "[x+y+z]" => "(vect (call-i x + y z))" "[x+y + z]" => "(vect (call-i x + y z))" # Dotted and normal operators - "a +₁ b +₁ c" => "(call-i (call-i a +₁ b) +₁ c)" - "a .+ b .+ c" => "(call-i (call-i a .+ b) .+ c)" + "a +₁ b +₁ c" => "(call-i (call-i a +₁ b) +₁ c)" + "a .+ b .+ c" => "(dotcall-i (dotcall-i a + b) + c)" ], JuliaSyntax.parse_term => [ "a * b * c" => "(call-i a * b c)" + "a .* b" => "(dotcall-i a * b)" "-2*x" => "(call-i -2 * x)" ], + JuliaSyntax.parse_rational => [ + "x // y // z" => "(call-i (call-i x // y) // z)" + ], + JuliaSyntax.parse_shift => [ + "x >> y >> z" => "(call-i (call-i x >> y) >> z)" + ], JuliaSyntax.parse_juxtapose => [ "2x" => "(call-i 2 * x)" "2x" => "(call-i 2 * x)" @@ -165,11 +180,6 @@ tests = [ "-2" => "-2" "+2.0" => "2.0" "-1.0f0" => "-1.0f0" - "-0x1" => "(call-pre - 0x01)" - "- 2" => "(call-pre - 2)" - ".-2" => "(call-pre .- 2)" - ], - JuliaSyntax.parse_unary_call => [ # Standalone dotted operators are parsed as (|.| op) ".+" => "(. +)" ".+\n" => "(. +)" @@ -181,8 +191,11 @@ tests = [ # Call with type parameters or non-unary prefix call "+{T}(x::T)" => "(call (curly + T) (:: x T))" "*(x)" => "(call * x)" + ".*(x)" => "(call .* x)" # Prefix function calls for operators which are both binary and unary "+(a,b)" => "(call + a b)" + ".+(a,)" => "(call .+ a)" + "(.+)(a)" => "(call (. +) a)" "+(a=1,)" => "(call + (= a 1))" => Expr(:call, :+, Expr(:kw, :a, 1)) "+(a...)" => "(call + (... a))" "+(a;b,c)" => "(call + a (parameters b c))" @@ -192,24 +205,33 @@ tests = [ # Prefix calls have higher precedence than ^ "+(a,b)^2" => "(call-i (call + a b) ^ 2)" "+(a,b)(x)^2" => "(call-i (call (call + a b) x) ^ 2)" + "<:(a,)" => "(<: a)" # Unary function calls with brackets as grouping, not an arglist + ".+(a)" => "(dotcall-pre + a)" "+(a;b)" => "(call-pre + (block a b))" "+(a=1)" => "(call-pre + (= a 1))" => Expr(:call, :+, Expr(:(=), :a, 1)) # Unary operators have lower precedence than ^ "+(a)^2" => "(call-pre + (call-i a ^ 2))" + ".+(a)^2" => "(dotcall-pre + (call-i a ^ 2))" "+(a)(x,y)^2" => "(call-pre + (call-i (call a x y) ^ 2))" - # Normal unary calls (see parse_unary) + "<:(a)" => "(<:-pre a)" + # Normal unary calls "+x" => "(call-pre + x)" "√x" => "(call-pre √ x)" - "±x" => "(call-pre ± x)" + ".~x" => "(dotcall-pre ~ x)" + # Things which are not quite negative literals + "-0x1"=> "(call-pre - 0x01)" + "- 2" => "(call-pre - 2)" + ".-2" => "(dotcall-pre - 2)" # Not a unary operator "/x" => "(call-pre (error /) x)" "+₁ x" => "(call-pre (error +₁) x)" - ".<: x" => "(call-pre (error .<:) x)" + ".<: x" => "(dotcall-pre (error .<:) x)" ], JuliaSyntax.parse_factor => [ "x^y" => "(call-i x ^ y)" "x^y^z" => "(call-i x ^ (call-i y ^ z))" + "x .^ y" => "(dotcall-i x ^ y)" "begin x end::T" => "(:: (block x) T)" # parse_decl_with_initial_ex "a::b" => "(:: a b)" @@ -223,6 +245,7 @@ tests = [ "<: =" => "<:" "<:{T}(x::T)" => "(call (curly <: T) (:: x T))" "<:(x::T)" => "(<:-pre (:: x T))" + "<: x" => "(<:-pre x)" "<: A where B" => "(<:-pre (where A B))" # Really for parse_where "x where \n {T}" => "(where x T)" @@ -318,11 +341,11 @@ tests = [ "A.B.@x" => "(macrocall (. (. A (quote B)) (quote @x)))" "@A.B.x" => "(macrocall (. (. A (quote B)) (quote @x)))" "A.@B.x" => "(macrocall (. (. A (quote B)) (error-t) (quote @x)))" - "f.(a,b)" => "(. f (tuple a b))" - "f.(a=1; b=2)" => "(. f (tuple (= a 1) (parameters (= b 2))))" => + "f.(a,b)" => "(dotcall f a b)" + "f.(a=1; b=2)" => "(dotcall f (= a 1) (parameters (= b 2)))" => Expr(:., :f, Expr(:tuple, Expr(:parameters, Expr(:kw, :b, 2)), Expr(:kw, :a, 1))) - "(a=1).()" => "(. (= a 1) (tuple))" => Expr(:., Expr(:(=), :a, 1), Expr(:tuple)) - "f. (x)" => "(. f (error-t) (tuple x))" + "(a=1).()" => "(dotcall (= a 1))" => Expr(:., Expr(:(=), :a, 1), Expr(:tuple)) + "f. (x)" => "(dotcall f (error-t) x)" # Other dotted syntax "A.:+" => "(. A (quote +))" "A.: +" => "(. A (quote (error-t) +))"