Skip to content

Commit 5ff59c2

Browse files
authored
Add TRAILING_COMMA_FLAG to distinguish (a,b) vs (a,b,) (#521)
This syntax flag allows the stylistic choice of adding trailing commas to be easily detected. For example, `f(x)` vs `f(x,)` and `(a,b)` vs `(a,b,)`.
1 parent 2d19c06 commit 5ff59c2

File tree

4 files changed

+73
-39
lines changed

4 files changed

+73
-39
lines changed

docs/src/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ JuliaSyntax.has_flags
115115
JuliaSyntax.TRIPLE_STRING_FLAG
116116
JuliaSyntax.RAW_STRING_FLAG
117117
JuliaSyntax.PARENS_FLAG
118+
JuliaSyntax.TRAILING_COMMA_FLAG
118119
JuliaSyntax.COLON_QUOTE
119120
JuliaSyntax.TOPLEVEL_SEMICOLONS_FLAG
120121
JuliaSyntax.MUTABLE_FLAG

src/parse_stream.jl

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ Set for K"tuple", K"block" or K"macrocall" which are delimited by parentheses
3939
"""
4040
const PARENS_FLAG = RawFlags(1<<5)
4141

42+
"""
43+
Set for various delimited constructs when they contains a trailing comma. For
44+
example, to distinguish `(a,b,)` vs `(a,b)`, and `f(a)` vs `f(a,)`. Kinds where
45+
this applies are: `tuple call dotcall macrocall vect curly braces <: >:`.
46+
"""
47+
const TRAILING_COMMA_FLAG = RawFlags(1<<6)
48+
4249
"""
4350
Set for K"quote" for the short form `:x` as opposed to long form `quote x end`
4451
"""
@@ -139,22 +146,27 @@ function untokenize(head::SyntaxHead; unique=true, include_flag_suff=true)
139146
is_prefix_op_call(head) && (str = str*"-pre")
140147
is_postfix_op_call(head) && (str = str*"-post")
141148

142-
if kind(head) in KSet"string cmdstring Identifier"
149+
k = kind(head)
150+
if k in KSet"string cmdstring Identifier"
143151
has_flags(head, TRIPLE_STRING_FLAG) && (str = str*"-s")
144152
has_flags(head, RAW_STRING_FLAG) && (str = str*"-r")
145-
elseif kind(head) in KSet"tuple block macrocall"
153+
elseif k in KSet"tuple block macrocall"
146154
has_flags(head, PARENS_FLAG) && (str = str*"-p")
147-
elseif kind(head) == K"quote"
155+
elseif k == K"quote"
148156
has_flags(head, COLON_QUOTE) && (str = str*"-:")
149-
elseif kind(head) == K"toplevel"
157+
elseif k == K"toplevel"
150158
has_flags(head, TOPLEVEL_SEMICOLONS_FLAG) && (str = str*"-;")
151-
elseif kind(head) == K"function"
159+
elseif k == K"function"
152160
has_flags(head, SHORT_FORM_FUNCTION_FLAG) && (str = str*"-=")
153-
elseif kind(head) == K"struct"
161+
elseif k == K"struct"
154162
has_flags(head, MUTABLE_FLAG) && (str = str*"-mut")
155-
elseif kind(head) == K"module"
163+
elseif k == K"module"
156164
has_flags(head, BARE_MODULE_FLAG) && (str = str*"-bare")
157165
end
166+
if k in KSet"tuple call dotcall macrocall vect curly braces <: >:" &&
167+
has_flags(head, TRAILING_COMMA_FLAG)
168+
str *= "-,"
169+
end
158170
is_suffixed(head) && (str = str*"-suf")
159171
n = numeric_flags(head)
160172
n != 0 && (str = str*"-"*string(n))

src/parser.jl

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,10 +1302,10 @@ function parse_unary(ps::ParseState)
13021302
# +(a,b)(x)^2 ==> (call-i (call (call + a b) x) ^ 2)
13031303
if is_type_operator(op_t)
13041304
# <:(a,) ==> (<: a)
1305-
emit(ps, mark, op_k)
1305+
emit(ps, mark, op_k, opts.delim_flags)
13061306
reset_node!(ps, op_pos, flags=TRIVIA_FLAG)
13071307
else
1308-
emit(ps, mark, K"call")
1308+
emit(ps, mark, K"call", opts.delim_flags)
13091309
end
13101310
parse_call_chain(ps, mark)
13111311
parse_factor_with_initial_ex(ps, mark)
@@ -1552,13 +1552,14 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
15521552
# f (a) ==> (call f (error-t) a)
15531553
bump_disallowed_space(ps)
15541554
bump(ps, TRIVIA_FLAG)
1555-
parse_call_arglist(ps, K")")
1555+
opts = parse_call_arglist(ps, K")")
15561556
if peek(ps) == K"do"
15571557
# f(x) do y body end ==> (call f x (do (tuple y) (block body)))
15581558
parse_do(ps)
15591559
end
15601560
emit(ps, mark, is_macrocall ? K"macrocall" : K"call",
1561-
is_macrocall ? PARENS_FLAG : EMPTY_FLAGS)
1561+
# TODO: Add PARENS_FLAG to all calls which use them?
1562+
(is_macrocall ? PARENS_FLAG : EMPTY_FLAGS)|opts.delim_flags)
15621563
if is_macrocall
15631564
# @x(a, b) ==> (macrocall-p @x a b)
15641565
# A.@x(y) ==> (macrocall-p (. A @x) y)
@@ -1634,8 +1635,8 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
16341635
# f. (x) ==> (dotcall f (error-t) x)
16351636
bump_disallowed_space(ps)
16361637
bump(ps, TRIVIA_FLAG)
1637-
parse_call_arglist(ps, K")")
1638-
emit(ps, mark, K"dotcall")
1638+
opts = parse_call_arglist(ps, K")")
1639+
emit(ps, mark, K"dotcall", opts.delim_flags)
16391640
elseif k == K":"
16401641
# A.:+ ==> (. A (quote-: +))
16411642
# A.: + ==> (. A (error-t) (quote-: +))
@@ -1697,20 +1698,20 @@ function parse_call_chain(ps::ParseState, mark, is_macrocall=false)
16971698
# S {a} ==> (curly S (error-t) a)
16981699
bump_disallowed_space(ps)
16991700
bump(ps, TRIVIA_FLAG)
1700-
parse_call_arglist(ps, K"}")
1701+
opts = parse_call_arglist(ps, K"}")
17011702
if is_macrocall
17021703
# @S{a,b} ==> (macrocall S (braces a b))
17031704
# A.@S{a} ==> (macrocall (. A @S) (braces a))
17041705
# @S{a}.b ==> (. (macrocall @S (braces a)) b)
17051706
fix_macro_name_kind!(ps, macro_name_position)
1706-
emit(ps, m, K"braces")
1707+
emit(ps, m, K"braces", opts.delim_flags)
17071708
emit(ps, mark, K"macrocall")
17081709
min_supported_version(v"1.6", ps, mark, "macro call without space before `{}`")
17091710
is_macrocall = false
17101711
macro_atname_range = nothing
17111712
else
17121713
# S{a,b} ==> (curly S a b)
1713-
emit(ps, mark, K"curly")
1714+
emit(ps, mark, K"curly", opts.delim_flags)
17141715
end
17151716
elseif k in KSet" \" \"\"\" ` ``` " &&
17161717
!preceding_whitespace(t) && maybe_strmac &&
@@ -2151,7 +2152,7 @@ function parse_function_signature(ps::ParseState, is_function::Bool)
21512152
# function (f(x),) end ==> (function (tuple-p (call f x)) (block))
21522153
ambiguous_parens = opts.maybe_grouping_parens &&
21532154
peek_behind(ps).kind in KSet"macrocall $"
2154-
emit(ps, mark, K"tuple", PARENS_FLAG)
2155+
emit(ps, mark, K"tuple", PARENS_FLAG|opts.delim_flags)
21552156
if ambiguous_parens
21562157
# Got something like `(@f(x))`. Is it anon `(@f(x),)` or named sig `@f(x)` ??
21572158
emit(ps, mark, K"error", error="Ambiguous signature. Add a trailing comma if this is a 1-argument anonymous function; remove parentheses if this is a macro call acting as function signature.")
@@ -2716,16 +2717,21 @@ end
27162717
# surrounding brackets.
27172718
#
27182719
# flisp: parse-vect
2719-
function parse_vect(ps::ParseState, closer)
2720+
function parse_vect(ps::ParseState, closer, prefix_trailing_comma)
27202721
# [x, y] ==> (vect x y)
27212722
# [x, y] ==> (vect x y)
27222723
# [x,y ; z] ==> (vect x y (parameters z))
27232724
# [x=1, y=2] ==> (vect (= x 1) (= y 2))
27242725
# [x=1, ; y=2] ==> (vect (= x 1) (parameters (= y 2)))
2725-
parse_brackets(ps, closer) do _, _, _, _
2726-
return (needs_parameters=true,)
2726+
opts = parse_brackets(ps, closer) do _, _, _, num_subexprs
2727+
return (needs_parameters=true,
2728+
num_subexprs=num_subexprs)
2729+
end
2730+
delim_flags = opts.delim_flags
2731+
if opts.num_subexprs == 0 && prefix_trailing_comma
2732+
delim_flags |= TRAILING_COMMA_FLAG
27272733
end
2728-
return (K"vect", EMPTY_FLAGS)
2734+
return (K"vect", delim_flags)
27292735
end
27302736

27312737
# Parse generators
@@ -2988,7 +2994,7 @@ function parse_cat(ps::ParseState, closer, end_is_symbol)
29882994
mark = position(ps)
29892995
if k == closer
29902996
# [] ==> (vect)
2991-
return parse_vect(ps, closer)
2997+
return parse_vect(ps, closer, false)
29922998
elseif k == K";"
29932999
#v1.8: [;] ==> (ncat-1)
29943000
#v1.8: [;;] ==> (ncat-2)
@@ -3003,14 +3009,15 @@ function parse_cat(ps::ParseState, closer, end_is_symbol)
30033009
parse_eq_star(ps)
30043010
k = peek(ps, skip_newlines=true)
30053011
if k == K"," || (is_closing_token(ps, k) && k != K";")
3006-
if k == K","
3012+
prefix_trailing_comma = k == K","
3013+
if prefix_trailing_comma
30073014
# [x,] ==> (vect x)
30083015
bump(ps, TRIVIA_FLAG; skip_newlines = true)
30093016
end
30103017
# [x] ==> (vect x)
30113018
# [x \n ] ==> (vect x)
30123019
# [x ==> (vect x (error-t))
3013-
parse_vect(ps, closer)
3020+
parse_vect(ps, closer, prefix_trailing_comma)
30143021
elseif k == K"for"
30153022
# [x for a in as] ==> (comprehension (generator x (iteration (in a as))))
30163023
# [x \n\n for a in as] ==> (comprehension (generator x (iteration (in a as))))
@@ -3087,7 +3094,7 @@ function parse_paren(ps::ParseState, check_identifiers=true)
30873094
# (; a=1; b=2) ==> (tuple-p (parameters (= a 1)) (parameters (= b 2)))
30883095
# (a; b; c,d) ==> (tuple-p a (parameters b) (parameters c d))
30893096
# (a=1, b=2; c=3) ==> (tuple-p (= a 1) (= b 2) (parameters (= c 3)))
3090-
emit(ps, mark, K"tuple", PARENS_FLAG)
3097+
emit(ps, mark, K"tuple", PARENS_FLAG|opts.delim_flags)
30913098
elseif opts.is_block
30923099
# Blocks
30933100
# (;;) ==> (block-p)
@@ -3135,6 +3142,7 @@ function parse_brackets(after_parse::Function,
31353142
had_commas = false
31363143
had_splat = false
31373144
param_start = nothing
3145+
trailing_comma = false
31383146
while true
31393147
k = peek(ps)
31403148
if k == closing_kind
@@ -3150,11 +3158,13 @@ function parse_brackets(after_parse::Function,
31503158
bump(ps, TRIVIA_FLAG)
31513159
bump_trivia(ps)
31523160
elseif is_closing_token(ps, k)
3161+
trailing_comma = false
31533162
# Error; handled below in bump_closing_token
31543163
break
31553164
else
31563165
mark = position(ps)
31573166
parse_eq_star(ps)
3167+
trailing_comma = false
31583168
num_subexprs += 1
31593169
if num_subexprs == 1
31603170
had_splat = peek_behind(ps).kind == K"..."
@@ -3172,6 +3182,7 @@ function parse_brackets(after_parse::Function,
31723182
if k == K","
31733183
had_commas = true
31743184
bump(ps, TRIVIA_FLAG)
3185+
trailing_comma = true
31753186
elseif k == K";" || k == closing_kind
31763187
# Handled above
31773188
continue
@@ -3193,7 +3204,7 @@ function parse_brackets(after_parse::Function,
31933204
end
31943205
release_positions(ps.stream, params_positions)
31953206
bump_closing_token(ps, closing_kind, " or `,`")
3196-
return opts
3207+
return (; opts..., delim_flags=trailing_comma ? TRAILING_COMMA_FLAG : EMPTY_FLAGS)
31973208
end
31983209

31993210
_is_indentation(b::UInt8) = (b == u8" " || b == u8"\t")
@@ -3420,14 +3431,15 @@ end
34203431
function emit_braces(ps, mark, ckind, cflags)
34213432
if ckind == K"hcat"
34223433
# {x y} ==> (bracescat (row x y))
3423-
emit(ps, mark, K"row", cflags)
3434+
emit(ps, mark, K"row", cflags & ~TRAILING_COMMA_FLAG)
34243435
elseif ckind == K"ncat"
34253436
# {x ;;; y} ==> (bracescat (nrow-3 x y))
3426-
emit(ps, mark, K"nrow", cflags)
3437+
emit(ps, mark, K"nrow", cflags & ~TRAILING_COMMA_FLAG)
34273438
end
34283439
check_ncat_compat(ps, mark, ckind)
34293440
outk = ckind in KSet"vect comprehension" ? K"braces" : K"bracescat"
3430-
emit(ps, mark, outk)
3441+
delim_flags = outk == K"braces" ? (cflags & TRAILING_COMMA_FLAG) : EMPTY_FLAGS
3442+
emit(ps, mark, outk, delim_flags)
34313443
end
34323444

34333445
# parse numbers, identifiers, parenthesized expressions, lists, vectors, etc.

test/parser.jl

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,10 @@ tests = [
234234
".*(x)" => "(call (. *) x)"
235235
# Prefix function calls for operators which are both binary and unary
236236
"+(a,b)" => "(call + a b)"
237-
".+(a,)" => "(call (. +) a)"
237+
"+(a,)" => "(call-, + a)"
238+
".+(a,)" => "(call-, (. +) a)"
238239
"(.+)(a)" => "(call (parens (. +)) a)"
239-
"+(a=1,)" => "(call + (= a 1))"
240+
"+(a=1,)" => "(call-, + (= a 1))"
240241
"+(a...)" => "(call + (... a))"
241242
"+(a;b,c)" => "(call + a (parameters b c))"
242243
"+(;a)" => "(call + (parameters a))"
@@ -251,7 +252,7 @@ tests = [
251252
# Prefix calls have higher precedence than ^
252253
"+(a,b)^2" => "(call-i (call + a b) ^ 2)"
253254
"+(a,b)(x)^2" => "(call-i (call (call + a b) x) ^ 2)"
254-
"<:(a,)" => "(<: a)"
255+
"<:(a,)" => "(<:-, a)"
255256
# Unary function calls with brackets as grouping, not an arglist
256257
".+(a)" => "(dotcall-pre + (parens a))"
257258
"+(a;b)" => "(call-pre + (block-p a b))"
@@ -306,6 +307,7 @@ tests = [
306307
# Really for parse_where
307308
"x where \n {T}" => "(where x (braces T))"
308309
"x where {T,S}" => "(where x (braces T S))"
310+
"x where {T,S,}" => "(where x (braces-, T S))"
309311
"x where {T S}" => "(where x (bracescat (row T S)))"
310312
"x where {y for y in ys}" => "(where x (braces (generator y (iteration (in y ys)))))"
311313
"x where T" => "(where x T)"
@@ -364,11 +366,13 @@ tests = [
364366

365367
# calls with brackets
366368
"f(a,b)" => "(call f a b)"
369+
"f(a,)" => "(call-, f a)"
367370
"f(a=1; b=2)" => "(call f (= a 1) (parameters (= b 2)))"
368371
"f(a; b; c)" => "(call f a (parameters b) (parameters c))"
369372
"(a=1)()" => "(call (parens (= a 1)))"
370373
"f (a)" => "(call f (error-t) a)"
371374
"@x(a, b)" => "(macrocall-p @x a b)"
375+
"@x(a, b,)" => "(macrocall-p-, @x a b)"
372376
"A.@x(y)" => "(macrocall-p (. A @x) y)"
373377
"A.@x(y).z" => "(. (macrocall-p (. A @x) y) z)"
374378
"f(y for x = xs; a)" => "(call f (generator y (iteration (in x xs))) (parameters a))"
@@ -407,6 +411,7 @@ tests = [
407411
"[email protected]" => "(macrocall (. (. A B) (error-t) @x))"
408412
"@M.(x)" => "(macrocall (dotcall @M (error-t) x))"
409413
"f.(a,b)" => "(dotcall f a b)"
414+
"f.(a,b,)" => "(dotcall-, f a b)"
410415
"f.(a=1; b=2)" => "(dotcall f (= a 1) (parameters (= b 2)))"
411416
"(a=1).()" => "(dotcall (parens (= a 1)))"
412417
"f. (x)" => "(dotcall f (error-t) x)"
@@ -577,9 +582,10 @@ tests = [
577582
"macro (\$f)() end" => "(macro (call (parens (\$ f))) (block))"
578583
"function (x) body end"=> "(function (tuple-p x) (block body))"
579584
"function (x,y) end" => "(function (tuple-p x y) (block))"
585+
"function (x,y,) end" => "(function (tuple-p-, x y) (block))"
580586
"function (x=1) end" => "(function (tuple-p (= x 1)) (block))"
581587
"function (;x=1) end" => "(function (tuple-p (parameters (= x 1))) (block))"
582-
"function (f(x),) end" => "(function (tuple-p (call f x)) (block))"
588+
"function (f(x),) end" => "(function (tuple-p-, (call f x)) (block))"
583589
"function (@f(x);) end" => "(function (tuple-p (macrocall-p @f x) (parameters)) (block))"
584590
"function (@f(x)...) end" => "(function (tuple-p (... (macrocall-p @f x))) (block))"
585591
"function (@f(x)) end" => "(function (error (tuple-p (macrocall-p @f x))) (block))"
@@ -715,7 +721,7 @@ tests = [
715721
JuliaSyntax.parse_paren => [
716722
# Tuple syntax with commas
717723
"()" => "(tuple-p)"
718-
"(x,)" => "(tuple-p x)"
724+
"(x,)" => "(tuple-p-, x)"
719725
"(x,y)" => "(tuple-p x y)"
720726
"(x=1, y=2)" => "(tuple-p (= x 1) (= y 2))"
721727
# Named tuples with initial semicolon
@@ -827,11 +833,12 @@ tests = [
827833
"=" => "(error =)"
828834
# parse_cat
829835
"[]" => "(vect)"
830-
"[x,]" => "(vect x)"
831-
"[x\n,,]" => "(vect x (error-t ✘))"
836+
"[x,]" => "(vect-, x)"
837+
"[x,y,]" => "(vect-, x y)"
838+
"[x\n,,]" => "(vect-, x (error-t ✘))"
832839
"[x]" => "(vect x)"
833840
"[x \n ]" => "(vect x)"
834-
"[x \n, ]" => "(vect x)"
841+
"[x \n, ]" => "(vect-, x)"
835842
"[x" => "(vect x (error-t))"
836843
"[x \n\n ]" => "(vect x)"
837844
"[x for a in as]" => "(comprehension (generator x (iteration (in a as))))"
@@ -849,10 +856,10 @@ tests = [
849856
"(x for a in as if z)" => "(parens (generator x (filter (iteration (in a as)) z)))"
850857
# parse_vect
851858
"[x, y]" => "(vect x y)"
852-
"[x, y]" => "(vect x y)"
859+
"[x, y,]" => "(vect-, x y)"
853860
"[x,\n y]" => "(vect x y)"
854861
"[x\n, y]" => "(vect x y)"
855-
"[x\n,, y]" => "(vect x (error-t ✘ y))"
862+
"[x\n,, y]" => "(vect-, x (error-t ✘ y))"
856863
"[x,y ; z]" => "(vect x y (parameters z))"
857864
"[x=1, y=2]" => "(vect (= x 1) (= y 2))"
858865
"[x=1, ; y=2]" => "(vect (= x 1) (parameters (= y 2)))"
@@ -862,6 +869,8 @@ tests = [
862869
":(::\n)" => "(quote-: (parens ::))"
863870
"(function f \n end)" => "(parens (function f))"
864871
# braces
872+
"{x,y}" => "(braces x y)"
873+
"{x,y,}" => "(braces-, x y)"
865874
"{x y}" => "(bracescat (row x y))"
866875
((v=v"1.7",), "{x ;;; y}") => "(bracescat (nrow-3 x y))"
867876
# Macro names can be keywords

0 commit comments

Comments
 (0)