Skip to content

Commit 5e6b789

Browse files
authored
Add support for decorators in defn and support conditional Python version compilation (#586)
* Add support for function decorators on `defn` * Add reader conditional for Python major/minor version * Do a docstring
1 parent 2dcc17e commit 5e6b789

File tree

3 files changed

+81
-13
lines changed

3 files changed

+81
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
* Added support for namespaced map syntax (#577)
1111
* Added support for numeric constant literals for NaN, positive infinity, and negative infinity (#582)
1212
* Added `*basilisp-version*` and `*python-version*` Vars to `basilisp.core` (#584)
13+
* Added support for function decorators to `defn` (#585)
14+
* Added the current Python version (`:lpy36`, `:lpy37`, etc.) as a default reader feature for reader conditionals (#585)
1315

1416
### Fixed
1517
* Fixed a bug where `def` forms did not permit recursive references to the `def`'ed Vars (#578)

src/basilisp/core.lpy

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,40 @@
276276

277277
(def
278278
^{:macro true
279-
:doc "Define a new function with an optional docstring."
279+
:doc "Define a new function with an optional docstring.
280+
281+
The function will be interned in the current Namespace as a Var using
282+
the given `name`.
283+
284+
After the name, an optional mapping of meta attributes may be provided.
285+
Any metadata values given will be attached to the metadata of the
286+
interned Var. A few special meta keys change how `defn` emits the
287+
final Var and function:
288+
289+
- `:decorators` is an optional vector of functions which will wrap
290+
the final function emitted by `defn`. Like standard Python decorators
291+
and the `comp` function, decorators are applied to the generated
292+
function from right to left.
293+
294+
Specify an optional docstring after the metadata map to provide callers
295+
with additional details about your function, its arguments, and its
296+
return value. If a docstring is provided, it will be attached to the
297+
interned Var metadata under the `:doc` key.
298+
299+
Functions may be defined with 1 or more arities. Functions of a single
300+
arity may be defined with a single vector of 0 or more arguments after
301+
the optional metadata map. Any forms appearing after the argument
302+
vector will be part of the function body. It is legal to define a
303+
function with no body. In this case, your function will always return
304+
`nil`.
305+
306+
Functions with multiple arities are defined as a series of lists after
307+
the optional metadata map. Each list must contain an argument vector
308+
and 0 or more body forms. No arity may share the same number of fixed
309+
(non-variadic) arguments. There may be at most one variadic arity
310+
defined per function and the number of fixed arguments to that arity
311+
must be greater than or equal than the number of fixed arguments of all
312+
other arities."
280313
:arglists '([name & body] [name doc? & body] [name doc? attr-map? & body])}
281314
defn
282315
(fn defn [&env &form name & body]
@@ -291,15 +324,15 @@
291324
body (if doc
292325
(rest body)
293326
body)
294-
fmeta (if (map? (first body))
295-
(first body)
296-
nil)
327+
fmeta (if (map? (first body))
328+
(first body)
329+
nil)
297330
fname (if fmeta
298331
(vary-meta name conj fmeta)
299332
name)
300333
fname (if doc
301334
(vary-meta fname assoc :doc doc)
302-
(vary-meta fname conj fmeta))
335+
fname)
303336
body (if fmeta
304337
(rest body)
305338
body)
@@ -321,10 +354,18 @@
321354
(throw
322355
(ex-info "Expected an argument vector"
323356
{:found (first body)})))
324-
(rest body)))]
325-
`(def ~fname
326-
(fn ~fname
327-
~@body)))))
357+
(rest body)))
358+
359+
decorators (:decorators fmeta)
360+
fn-body (if decorators
361+
(loop [wrappers (seq (python/reversed decorators))
362+
final `(fn ~fname ~@body)]
363+
(if (seq wrappers)
364+
(recur (rest wrappers)
365+
`(~(first wrappers) ~final))
366+
final))
367+
`(fn ~fname ~@body))]
368+
`(def ~fname ~fn-body))))
328369

329370
(defn nth
330371
"Returns the `i`th element of `coll` (0-indexed), if it exists or `nil`
@@ -417,8 +458,24 @@
417458

418459
(def
419460
^{:macro true
420-
:doc "Define a new macro like defn. Macro functions are available to the
421-
compiler during macroexpansion."
461+
:doc "Define a new macro function. The arguments and syntax of `defmacro`
462+
are identical to that of `defn`.
463+
464+
When the compiler encounters a new macro function invocation, it
465+
immediately invokes that function during compilation and substitutes
466+
the function invocation with the return value of the called macro.
467+
Macros must return valid syntax at the point they are invoked,
468+
otherwise the compiler will throw an exception and compilation
469+
will halt.
470+
471+
Macros created by `defmacro` have access to two implicit arguments
472+
whose names must not appear in your argument list:
473+
474+
- `&env` is a map of all symbol bindings available to the compiler
475+
at the point the macro is invoked.
476+
- `&form` is the original form invoking the macro. This is often
477+
useful for reading and copying metadata attached to the original
478+
form."
422479
:arglists '([name & body] [name doc? & body] [name doc? attr-map? & body])}
423480
defmacro
424481
(fn defmacro [&env &form name & body]
@@ -482,7 +539,10 @@
482539
~@body))
483540

484541
(defmacro defn-
485-
"Define a new private function as by `defn`."
542+
"Define a new private function as by `defn`.
543+
544+
Private functions are `def`'ed with the `:private` metadata, which makes them
545+
ineligible for access outside the namespace using `require` or `refer`."
486546
[name & body]
487547
`(defn ~(vary-meta name assoc :private true)
488548
~@body))

src/basilisp/lang/reader.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import functools
55
import io
66
import re
7+
import sys
78
import uuid
89
from datetime import datetime
910
from fractions import Fraction
@@ -73,10 +74,15 @@
7374
READER_LINE_KW = keyword.keyword("line", ns="basilisp.lang.reader")
7475
READER_COL_KW = keyword.keyword("col", ns="basilisp.lang.reader")
7576

77+
READER_COND_BASILISP_PY_VERSION_FEATURE_KW = keyword.keyword(
78+
f"lpy{sys.version_info.major}{sys.version_info.minor}"
79+
)
7680
READER_COND_BASILISP_FEATURE_KW = keyword.keyword("lpy")
7781
READER_COND_DEFAULT_FEATURE_KW = keyword.keyword("default")
7882
READER_COND_DEFAULT_FEATURE_SET = lset.s(
79-
READER_COND_BASILISP_FEATURE_KW, READER_COND_DEFAULT_FEATURE_KW
83+
READER_COND_BASILISP_FEATURE_KW,
84+
READER_COND_DEFAULT_FEATURE_KW,
85+
READER_COND_BASILISP_PY_VERSION_FEATURE_KW,
8086
)
8187
READER_COND_FORM_KW = keyword.keyword("form")
8288
READER_COND_SPLICING_KW = keyword.keyword("splicing?")

0 commit comments

Comments
 (0)