Skip to content

Commit 75a01d3

Browse files
authored
Support Shebang style line comments (#469)
* Support Shebang style line comments * Changelog
1 parent fa5da10 commit 75a01d3

File tree

6 files changed

+46
-16
lines changed

6 files changed

+46
-16
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
* Added support for Shebang-style line comments (#469)
10+
811
### Changed
912
* Change the default user namespace to `basilisp.user` (#466)
1013

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# -- Project information -----------------------------------------------------
2121

2222
project = "Basilisp"
23-
copyright = "2018, Chris Rink"
23+
copyright = "2018-2020, Chris Rink"
2424
author = "Chris Rink"
2525

2626
# The short X.Y version

docs/reader.rst

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Reader
22
======
33

4-
In most Lisps, the reader is the component responsible for reading in thet extual representation of the program into memory as data structures.
4+
In most Lisps, the reader is the component responsible for reading in the textual representation of the program into memory as data structures.
55
Lisps are typically referred to as *homoiconic*, since that representation typically matches the syntax tree exactly in memory.
66
This is in contrast to a non-homoiconic language such as Java or Python, which typically parses a textual program into an abstract syntax tree which captures the *meaning* of the textual program, but not necessarily the structure.
77

@@ -26,11 +26,11 @@ Integers
2626

2727
basilisp.user=> 1
2828
1
29-
basilisp.user=> (builtins/type 1)
29+
basilisp.user=> (python/type 1)
3030
<class 'int'>
3131
basilisp.user=> 1N
3232
1
33-
basilisp.user=> (builtins/type 1N)
33+
basilisp.user=> (python/type 1N)
3434
<class 'int'>
3535

3636
Integers are represented using numeric ``0-9`` and may be prefixed with any number of negative signs ``-``.
@@ -45,11 +45,11 @@ Floating Point
4545

4646
basilisp.user=> 1.0
4747
1.0
48-
basilisp.user=> (builtins/type 1.0)
48+
basilisp.user=> (python/type 1.0)
4949
<class 'float'>
5050
basilisp.user=> 1M
5151
1
52-
basilisp.user=> (builtins/type 1M)
52+
basilisp.user=> (python/type 1M)
5353
<class 'decimal.Decimal'>
5454

5555
Floating point values are represented using ``0-9`` and a trailing decimal value, separated by a ``.`` character.
@@ -65,11 +65,11 @@ Complex
6565

6666
basilisp.user=> 1J
6767
1J
68-
basilisp.user=> (builtins/type 1J)
68+
basilisp.user=> (python/type 1J)
6969
<class 'complex'>
7070
basilisp.user=> 1.0J
7171
1J
72-
basilisp.user=> (builtins/type 1.0J)
72+
basilisp.user=> (python/type 1.0J)
7373
<class 'complex'>
7474

7575
Basilisp includes support for complex literals to match the Python VM hosts it.
@@ -87,7 +87,7 @@ Strings
8787
""
8888
basilisp.user=> "this is a string"
8989
"this is a string"
90-
basilisp.user=> (builtins/type "")
90+
basilisp.user=> (python/type "")
9191
<class 'str'>
9292

9393
Strings are denoted as a series of characters enclosed by ``"`` quotation marks.
@@ -128,11 +128,11 @@ Boolean Values
128128

129129
basilisp.user=> true
130130
true
131-
basilisp.user=> (builtins/type true)
131+
basilisp.user=> (python/type true)
132132
<class 'bool'>
133133
basilisp.user=> false
134134
false
135-
basilisp.user=> (builtins/type false)
135+
basilisp.user=> (python/type false)
136136
<class 'bool'>
137137

138138
The special values ``true`` and ``false`` correspond to Python's ``True`` and ``False`` respectively.
@@ -146,7 +146,7 @@ nil
146146

147147
basilisp.user=> nil
148148
nil
149-
basilisp.user=> (builtins/type nil)
149+
basilisp.user=> (python/type nil)
150150
<class 'NoneType'>
151151

152152
The special value ``nil`` correspond's to Python's ``None``.
@@ -270,6 +270,8 @@ Line Comments
270270
Line comments are specified with the ``;`` character.
271271
All of the text to the end of the line are ignored.
272272

273+
For a convenience in writing shell scripts with Basilisp, the standard *NIX `shebang <https://en.wikipedia.org/wiki/Shebang_(Unix)>` (``#!``) is also treated as a single-line comment.
274+
273275
.. _metadata:
274276

275277
Metadata
@@ -304,6 +306,7 @@ Reader macros are always dispatched using the ``#`` character.
304306

305307
* ``#'form`` is rewritten as ``(var form)``.
306308
* ``#_form`` causes the reader to completely ignore ``form``.
309+
* ``#!form`` is treated as a single-line comment (like ``;form``) as a convenience to support `shebangs <https://en.wikipedia.org/wiki/Shebang_(Unix)>` at the top of Basilisp scripts.
307310
* ``#"str"`` causes the reader to interpret ``"str"`` as a regex and return a Python `re.pattern <https://docs.python.org/3/library/re.html>`_.
308311
* ``#(...)`` causes the reader to interpret the contents of the list as an anonymous function. Anonymous functions specified in this way can name arguments using ``%1``, ``%2``, etc. and rest args as ``%&``. For anonymous functions with only one argument, ``%`` can be used in place of ``%1``.
309312

@@ -323,6 +326,18 @@ Basilisp supports a few builtin data readers:
323326
* ``#inst "2018-09-14T15:11:20.253-00:00"`` yields a Python `datetime <https://docs.python.org/3/library/datetime.html#datetime-objects>`_ object.
324327
* ``#uuid "c3598794-20b4-48db-b76e-242f4405743f"`` yields a Python `UUID <https://docs.python.org/3/library/uuid.html#uuid.UUID>`_ object.
325328

329+
One of the benefits of choosing Basilisp is convenient built-in Python language interop.
330+
However, the immutable data structures of Basilisp may not always play nicely with code written for (and expecting to be used by) other Python code.
331+
Fortunately, Basilisp includes data readers for reading Python collection literals directly from the REPL or from Basilisp source.
332+
333+
Python literals can be read by prefixing existing Basilisp data structures with a ``#py`` data reader tag.
334+
Python literals use the matching syntax to the corresponding Python data type, which does not always match the syntax for the same data type in Basilisp.
335+
336+
* ``#py []`` produces a Python `list <https://docs.python.org/3/library/stdtypes.html#list>` type.
337+
* ``#py ()`` produces a Python `tuple <https://docs.python.org/3/library/stdtypes.html#tuple>` type.
338+
* ``#py {}`` produces a Python `dict <https://docs.python.org/3/library/stdtypes.html#dict>` type.
339+
* ``#py #{}`` produces a Python `set <https://docs.python.org/3/library/stdtypes.html#set>` type.
340+
326341
.. _special_chars:
327342

328343
Special Characters

src/basilisp/lang/compiler/analyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ def current_macro_ns(self) -> Optional[runtime.Namespace]:
361361

362362
@contextlib.contextmanager
363363
def macro_ns(self, ns: Optional[runtime.Namespace]):
364-
"""Set the transient namespace which is available to the analyer during a
364+
"""Set the transient namespace which is available to the analyzer during a
365365
macroexpansion phase.
366366
367367
If set to None, prohibit the analyzer from using another namespace for symbol

src/basilisp/lang/reader.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ def _postwalk(f, form):
843843
def _read_function(ctx: ReaderContext) -> llist.List:
844844
"""Read a function reader macro from the input stream."""
845845
if ctx.is_in_anon_fn:
846-
raise SyntaxError(f"Nested #() definitions not allowed")
846+
raise SyntaxError("Nested #() definitions not allowed")
847847

848848
with ctx.in_anon_fn():
849849
form = _read_list(ctx)
@@ -1222,6 +1222,8 @@ def _read_reader_macro(ctx: ReaderContext) -> LispReaderForm:
12221222
ctx.reader.advance()
12231223
_read_next(ctx) # Ignore the entire next form
12241224
return COMMENT
1225+
elif token == "!":
1226+
return _read_comment(ctx)
12251227
elif token == "?":
12261228
return _read_reader_conditional(ctx)
12271229
elif ns_name_chars.match(token):
@@ -1244,7 +1246,7 @@ def _read_comment(ctx: ReaderContext) -> LispReaderForm:
12441246
Return the next form after the next line break."""
12451247
reader = ctx.reader
12461248
start = reader.advance()
1247-
assert start == ";"
1249+
assert start in {";", "!"}
12481250
while True:
12491251
token = reader.peek()
12501252
if newline_chars.match(token):
@@ -1307,7 +1309,7 @@ def _read_next(ctx: ReaderContext) -> LispReaderForm: # noqa: C901 MC0001
13071309
elif token == "":
13081310
return ctx.eof
13091311
else:
1310-
raise SyntaxError("Unexpected token '{token}'".format(token=token))
1312+
raise SyntaxError(f"Unexpected token '{token}'")
13111313

13121314

13131315
def read( # pylint: disable=too-many-arguments

tests/basilisp/reader_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,16 @@ def test_comment_line():
839839
)
840840

841841

842+
def test_shebang_line():
843+
assert None is read_str_first("#! I'm a little shebang short and stout")
844+
assert kw.keyword("kw2") == read_str_first("#!/usr/bin/env basilisp run\n:kw2")
845+
assert llist.l(sym.symbol("form"), kw.keyword("keyword")) == read_str_first(
846+
"""#!/usr/bin/env basilisp run
847+
(form :keyword)
848+
"""
849+
)
850+
851+
842852
class TestReaderConditional:
843853
@pytest.mark.parametrize(
844854
"v",

0 commit comments

Comments
 (0)