Skip to content

Commit a9110fa

Browse files
LilithHafnerc42f
authored and
Lilith Hafner
committed
Add public keyword (#320)
Add public as a contextual keyword that is parsed as a keyword only when it is both at the top-level and not followed by `(`, `=`, or `[`. Aside from this, the `public` keyword uses the same syntax as the `export` keyword and lowers analogously. Emit a warning when parsing `public` at the top-level followed by a `(`, `=`, or `[`. Co-authored-by: Claire Foster <[email protected]>
1 parent 045d156 commit a9110fa

File tree

6 files changed

+63
-9
lines changed

6 files changed

+63
-9
lines changed

src/kinds.jl

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const _kind_names =
6969
"mutable"
7070
"outer"
7171
"primitive"
72+
"public"
7273
"type"
7374
"var"
7475
"END_CONTEXTUAL_KEYWORDS"

src/parser.jl

+22-4
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ end
482482
# flisp: parse-stmts
483483
function parse_stmts(ps::ParseState)
484484
mark = position(ps)
485-
do_emit = parse_Nary(ps, parse_docstring, (K";",), (K"NewlineWs",))
485+
do_emit = parse_Nary(ps, parse_public, (K";",), (K"NewlineWs",))
486486
# check for unparsed junk after an expression
487487
junk_mark = position(ps)
488488
while peek(ps) ∉ KSet"EndMarker NewlineWs"
@@ -499,6 +499,24 @@ function parse_stmts(ps::ParseState)
499499
end
500500
end
501501

502+
# Parse `public foo, bar`
503+
#
504+
# We *only* call this from toplevel contexts (file and module level) for
505+
# compatibility. In the future we should probably make public a full fledged
506+
# keyword like `export`.
507+
function parse_public(ps::ParseState)
508+
if ps.stream.version >= (1, 11) && peek(ps) == K"public"
509+
if peek(ps, 2) ∈ KSet"( = ["
510+
# this branch is for compatibility with use of public as a non-keyword.
511+
# it should be removed at some point.
512+
emit_diagnostic(ps, warning="using public as an identifier is deprecated")
513+
else
514+
return parse_resword(ps)
515+
end
516+
end
517+
parse_docstring(ps)
518+
end
519+
502520
# Parse docstrings attached by a space or single newline
503521
#
504522
# flisp: parse-docstring
@@ -1966,11 +1984,11 @@ function parse_resword(ps::ParseState)
19661984
end
19671985
# module A \n a \n b \n end ==> (module A (block a b))
19681986
# module A \n "x"\na \n end ==> (module A (block (doc (string "x") a)))
1969-
parse_block(ps, parse_docstring)
1987+
parse_block(ps, parse_public)
19701988
bump_closing_token(ps, K"end")
19711989
emit(ps, mark, K"module",
19721990
word == K"baremodule" ? BARE_MODULE_FLAG : EMPTY_FLAGS)
1973-
elseif word == K"export"
1991+
elseif word in KSet"export public"
19741992
# export a ==> (export a)
19751993
# export @a ==> (export @a)
19761994
# export a, \n @b ==> (export a @b)
@@ -1979,7 +1997,7 @@ function parse_resword(ps::ParseState)
19791997
# export \$a, \$(a*b) ==> (export (\$ a) (\$ (parens (call-i a * b))))
19801998
bump(ps, TRIVIA_FLAG)
19811999
parse_comma_separated(ps, x->parse_atsym(x, false))
1982-
emit(ps, mark, K"export")
2000+
emit(ps, mark, word)
19832001
elseif word in KSet"import using"
19842002
parse_imports(ps)
19852003
elseif word == K"do"

src/tokenize.jl

+1
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,7 @@ K"let",
13441344
K"local",
13451345
K"macro",
13461346
K"module",
1347+
K"public",
13471348
K"quote",
13481349
K"return",
13491350
K"struct",

test/diagnostics.jl

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
function diagnostic(str; only_first=false, allow_multiple=false, rule=:all)
2-
stream = ParseStream(str)
1+
function diagnostic(str; only_first=false, allow_multiple=false, rule=:all, version=v"1.6")
2+
stream = ParseStream(str; version=version)
33
parse!(stream, rule=rule)
44
if allow_multiple
55
stream.diagnostics
@@ -127,8 +127,12 @@ end
127127
Diagnostic(10, 13, :warning, "parentheses are not required here")
128128
@test diagnostic("export (x)") ==
129129
Diagnostic(8, 10, :warning, "parentheses are not required here")
130-
@test diagnostic("export :x") ==
130+
@test diagnostic("export :x") ==
131131
Diagnostic(8, 9, :error, "expected identifier")
132+
@test diagnostic("public = 4", version=v"1.11") ==
133+
diagnostic("public[7] = 5", version=v"1.11") ==
134+
diagnostic("public() = 6", version=v"1.11") ==
135+
Diagnostic(1, 6, :warning, "using public as an identifier is deprecated")
132136
end
133137

134138
@testset "diagnostics for literal parsing" begin

test/parser.jl

+31-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,22 @@ function test_parse(production, input, output)
1717
else
1818
opts = NamedTuple()
1919
end
20-
@test parse_to_sexpr_str(production, input; opts...) == output
20+
parsed = parse_to_sexpr_str(production, input; opts...)
21+
if output isa Regex # Could be AbstractPattern, but that type was added in Julia 1.6.
22+
@test match(output, parsed) !== nothing
23+
else
24+
@test parsed == output
25+
end
2126
end
2227

2328
function test_parse(inout::Pair)
2429
test_parse(JuliaSyntax.parse_toplevel, inout...)
2530
end
2631

32+
const PARSE_ERROR = r"\(error-t "
33+
34+
with_version(v::VersionNumber, (i,o)::Pair) = ((;v=v), i) => o
35+
2736
# TODO:
2837
# * Extract the following test cases from the source itself.
2938
# * Use only the green tree to generate the S-expressions
@@ -434,7 +443,7 @@ tests = [
434443
"x\"s\"in" => """(macrocall @x_str (string-r "s") "in")"""
435444
"x\"s\"2" => """(macrocall @x_str (string-r "s") 2)"""
436445
"x\"s\"10.0" => """(macrocall @x_str (string-r "s") 10.0)"""
437-
#
446+
#
438447
],
439448
JuliaSyntax.parse_resword => [
440449
# In normal_context
@@ -933,6 +942,26 @@ tests = [
933942
"10.0e1000'" => "(ErrorNumericOverflow)"
934943
"10.0f100'" => "(ErrorNumericOverflow)"
935944
],
945+
JuliaSyntax.parse_stmts => with_version.(v"1.11", [
946+
"function f(public)\n public + 3\nend" => "(function (call f public) (block (call-i public + 3)))"
947+
"public A, B" => "(public A B)"
948+
"if true \n public *= 4 \n end" => "(if true (block (*= public 4)))"
949+
"module Mod\n public A, B \n end" => "(module Mod (block (public A B)))"
950+
"module Mod2\n a = 3; b = 6; public a, b\n end" => "(module Mod2 (block (= a 3) (= b 6) (public a b)))"
951+
"a = 3; b = 6; public a, b" => "(toplevel-; (= a 3) (= b 6) (public a b))"
952+
"begin \n public A, B \n end" => PARSE_ERROR
953+
"if true \n public A, B \n end" => PARSE_ERROR
954+
"public export=true foo, bar" => PARSE_ERROR # but these may be
955+
"public experimental=true foo, bar" => PARSE_ERROR # supported soon ;)
956+
"public(x::String) = false" => "(= (call public (::-i x String)) false)"
957+
"module M; export @a; end" => "(module M (block (export @a)))"
958+
"module M; public @a; end" => "(module M (block (public @a)))"
959+
"module M; export ⤈; end" => "(module M (block (export ⤈)))"
960+
"module M; public ⤈; end" => "(module M (block (public ⤈)))"
961+
"public = 4" => "(= public 4)"
962+
"public[7] = 5" => "(= (ref public 7) 5)"
963+
"public() = 6" => "(= (call public) 6)"
964+
]),
936965
JuliaSyntax.parse_docstring => [
937966
""" "notdoc" ] """ => "(string \"notdoc\")"
938967
""" "notdoc" \n] """ => "(string \"notdoc\")"

test/tokenize.jl

+1
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,7 @@ const all_kws = Set([
932932
"local",
933933
"macro",
934934
"module",
935+
"public",
935936
"quote",
936937
"return",
937938
"struct",

0 commit comments

Comments
 (0)