def _read_list(ctx: ReaderContext) -> llist.List:
    """Read a list element from the input stream."""
    start = ctx.reader.advance()
    assert start == "("
    return _read_coll(ctx, llist.list, ")", "list")

def _read_list ( ctx : ReaderContext ) -> llist . List : start = ctx . reader . advance ( ) assert start == "(" return _read_coll ( ctx , llist . list , ")" , "list" )

Read a list element from the input stream .


def _read_vector(ctx: ReaderContext) -> vector.Vector:
    """Read a vector element from the input stream."""
    start = ctx.reader.advance()
    assert start == "["
    return _read_coll(ctx, vector.vector, "]", "vector")

def _read_vector ( ctx : ReaderContext ) -> vector . Vector : start = ctx . reader . advance ( ) assert start == "[" return _read_coll ( ctx , vector . vector , "]" , "vector" )

Read a vector element from the input stream .


def _read_set(ctx: ReaderContext) -> lset.Set:
    """Return a set from the input stream."""
    start = ctx.reader.advance()
    assert start == "{"

    def set_if_valid(s: Collection) -> lset.Set:
        if len(s) != len(set(s)):
            raise SyntaxError("Duplicated values in set")
        return lset.set(s)

    return _read_coll(ctx, set_if_valid, "}", "set")

def
_read_set
(
ctx
:
ReaderContext
)
->
lset
.
Set
:
start
=
ctx
.
reader
.
advance
(
)
assert
start
==
"{"
def
set_if_valid
(
s
:
Collection
)
->
lset
.
Set
:
if
len
(
s
)
!=
len
(
set
(
s
)
)
:
raise
SyntaxError
(
"Duplicated values in set"
)
return
lset
.
set
(
s
)
return
_read_coll
(
ctx
,
set_if_valid
,
"}"
,
"set"
)


def _read_map(ctx: ReaderContext) -> lmap.Map:
    """Return a map from the input stream."""
    reader = ctx.reader
    start = reader.advance()
    assert start == "{"
    d: MutableMapping[Any, Any] = {}
    while True:
        if reader.peek() == "}":
            reader.next_token()
            break
        k = _read_next(ctx)
        if k is COMMENT:
            continue
        while True:
            if reader.peek() == "}":
                raise SyntaxError("Unexpected token '}'; expected map value")
            v = _read_next(ctx)
            if v is COMMENT:
                continue
            if k in d:
                raise SyntaxError(f"Duplicate key '{k}' in map literal")
            break
        d[k] = v

    return lmap.map(d)


def _read_str(ctx: ReaderContext, allow_arbitrary_escapes: bool = False) -> str:
    """Return a string from the input stream.

    If allow_arbitrary_escapes is True, do not throw a SyntaxError if an
    unknown escape sequence is encountered."""
    s: List[str] = []
    reader = ctx.reader
    while True:
        token = reader.next_token()
        if token == "":
            raise SyntaxError("Unexpected EOF in string")
        if token == "\\":
            token = reader.next_token()
            escape_char = _STR_ESCAPE_CHARS.get(token, None)
            if escape_char:
                s.append(escape_char)
                continue
            if allow_arbitrary_escapes:
                s.append("\\")
            else:
                raise SyntaxError("Unknown escape sequence: \\{token}")
        if token == '"':
            reader.next_token()
            return "".join(s)
        s.append(token)

def
_read_str
(
ctx
:
ReaderContext
,
allow_arbitrary_escapes
:
bool
=
False
)
->
str
:
s
:
List
[
str
]
=
[
]
reader
=
ctx
.
reader
while
True
:
token
=
reader
.
next_token
(
)
if
token
==
""
:
raise
SyntaxError
(
"Unexpected EOF in string"
)
if
token
==
"\\"
:
token
=
reader
.
next_token
(
)
escape_char
=
_STR_ESCAPE_CHARS
.
get
(
token
,
)
if
escape_char
:
s
.
append
(
escape_char
)
continue
if
allow_arbitrary_escapes
:
s
.
append
(
"\\"
)
else
:
raise
SyntaxError
(
"Unknown escape sequence: \\{token}"
)
if
token
==
'"'
:
reader
.
next_token
(
)
return
""
.
join
(
s
)
s
.
append
(
token
)


def _read_sym(ctx: ReaderContext) -> MaybeSymbol:
    """Return a symbol from the input stream.

    If a symbol appears in a syntax quoted form, the reader will attempt
    to resolve the symbol using the resolver in the ReaderContext `ctx`.
    The resolver will look into the current namespace for an alias or
    namespace matching the symbol's namespace."""
    ns, name = _read_namespaced(ctx, allowed_suffix="#")
    if not ctx.is_syntax_quoted and name.endswith("#"):
        raise SyntaxError("Gensym may not appear outside syntax quote")
    if ns is not None:
        if any(map(lambda s: len(s) == 0, ns.split("."))):
            raise SyntaxError(
                "All '.' separated segments of a namespace "
                "must contain at least one character."
            )
    if name.startswith(".") and ns is not None:
        raise SyntaxError("Symbols starting with '.' may not have a namespace")
    if ns is None:
        if name == "nil":
            return None
        elif name == "true":
            return True
        elif name == "false":
            return False
    if ctx.is_syntax_quoted and not name.endswith("#"):
        return ctx.resolve(symbol.symbol(name, ns))
    return symbol.symbol(name, ns=ns)


def _read_kw(ctx: ReaderContext) -> keyword.Keyword:
    """Return a keyword from the input stream."""
    start = ctx.reader.advance()
    assert start == ":"
    ns, name = _read_namespaced(ctx)
    if "." in name:
        raise SyntaxError("Found '.' in keyword name")
    return keyword.keyword(name, ns=ns)

def
_read_kw
(
ctx
:
ReaderContext
)
->
keyword
.
Keyword
:
start
=
ctx
.
reader
.
advance
(
)
assert
start
==
":"
ns
,
name
=
_read_namespaced
(
ctx
)
if
"."
in
name
:
raise
SyntaxError
(
"Found '.' in keyword name"
)
return
keyword
.
keyword
(
name
,
ns
=
ns
)

Return a keyword from the input stream .


def _read_meta(ctx: ReaderContext) -> IMeta:
    """Read metadata and apply that to the next object in the
    input stream."""
    start = ctx.reader.advance()
    assert start == "^"
    meta = _read_next_consuming_comment(ctx)

    meta_map: Optional[lmap.Map[LispForm, LispForm]] = None
    if isinstance(meta, symbol.Symbol):
        meta_map = lmap.map({keyword.keyword("tag"): meta})
    elif isinstance(meta, keyword.Keyword):
        meta_map = lmap.map({meta: True})
    elif isinstance(meta, lmap.Map):
        meta_map = meta
    else:
        raise SyntaxError(
            f"Expected symbol, keyword, or map for metadata, not {type(meta)}"
        )

    obj_with_meta = _read_next_consuming_comment(ctx)
    try:
        return obj_with_meta.with_meta(meta_map)  # type: ignore
    except AttributeError:
        raise SyntaxError(
            f"Can not attach metadata to object of type {type(obj_with_meta)}"
        )

Read metadata and apply that to the next object in the input stream .


def _read_function(ctx: ReaderContext) -> llist.List:
    """Read a function reader macro from the input stream."""
    if ctx.is_in_anon_fn:
        raise SyntaxError(f"Nested #() definitions not allowed")

    with ctx.in_anon_fn():
        form = _read_list(ctx)
    arg_set = set()

    def arg_suffix(arg_num):
        if arg_num is None:
            return "1"
        elif arg_num == "&":
            return "rest"
        else:
            return arg_num

    def sym_replacement(arg_num):
        suffix = arg_suffix(arg_num)
        return symbol.symbol(f"arg-{suffix}")

    def identify_and_replace(f):
        if isinstance(f, symbol.Symbol):
            if f.ns is None:
                match = fn_macro_args.match(f.name)
                if match is not None:
                    arg_num = match.group(2)
                    suffix = arg_suffix(arg_num)
                    arg_set.add(suffix)
                    return sym_replacement(arg_num)
        return f

    body = walk.postwalk(identify_and_replace, form) if len(form) > 0 else None

    arg_list: List[symbol.Symbol] = []
    numbered_args = sorted(map(int, filter(lambda k: k != "rest", arg_set)))
    if len(numbered_args) > 0:
        max_arg = max(numbered_args)
        arg_list = [sym_replacement(str(i)) for i in range(1, max_arg + 1)]
        if "rest" in arg_set:
            arg_list.append(_AMPERSAND)
            arg_list.append(sym_replacement("rest"))

    return llist.l(_FN, vector.vector(arg_list), body)

def
_read_function
(
ctx
:
ReaderContext
)
->
llist
.
List
:
if
ctx
.
is_in_anon_fn
:
raise
SyntaxError
(
f"Nested #() definitions not allowed"
)
with
ctx
.
in_anon_fn
(
)
:
form
=
_read_list
(
ctx
)
arg_set
=
set
(
)
def
arg_suffix
(
arg_num
)
:
if
arg_num
is
:
return
"1"
elif
arg_num
==
"&"
:
return
"rest"
else
:
return
arg_num
def
sym_replacement
(
arg_num
)
:
suffix
=
arg_suffix
(
arg_num
)
return
symbol
.
symbol
(
f"arg-{suffix}"
)
def
identify_and_replace
(
f
)
:
if
isinstance
(
f
,
symbol
.
Symbol
)
:
if
f
.
ns
is
:
match
=
fn_macro_args
.
match
(
f
.
name
)
if
match
is
not
:
arg_num
=
match
.
group
(
2
)
suffix
=
arg_suffix
(
arg_num
)
arg_set
.
add
(
suffix
)
return
sym_replacement
(
arg_num
)
return
f
body
=
walk
.
postwalk
(
identify_and_replace
,
form
)
if
len
(
form
)
>
0
else
arg_list
:
List
[
symbol
.
Symbol
]
=
[
]
numbered_args
=
sorted
(
map
(
int
,
filter
(
lambda
k
:
k
!=
"rest"
,
arg_set
)
)
)
if
len
(
numbered_args
)
>
0
:
max_arg
=
max
(
numbered_args
)
arg_list
=
[
sym_replacement
(
str
(
i
)
)
for
i
in
range
(
1
,
max_arg
+
1
)
]
if
"rest"
in
arg_set
:
arg_list
.
append
(
_AMPERSAND
)
arg_list
.
append
(
sym_replacement
(
"rest"
)
)
return
llist
.
l
(
_FN
,
vector
.
vector
(
arg_list
)
,
body
)

Read a function reader macro from the input stream .


def _read_quoted(ctx: ReaderContext) -> llist.List:
    """Read a quoted form from the input stream."""
    start = ctx.reader.advance()
    assert start == "'"
    next_form = _read_next_consuming_comment(ctx)
    return llist.l(_QUOTE, next_form)

def _read_quoted ( ctx : ReaderContext ) -> llist . List : start = ctx . reader . advance ( ) assert start == "'" next_form = _read_next_consuming_comment ( ctx ) return llist . l ( _QUOTE , next_form )

Read a quoted form from the input stream .


def _expand_syntax_quote(
    ctx: ReaderContext, form: IterableLispForm
) -> Iterable[LispForm]:
    """Expand syntax quoted forms to handle unquoting and unquote-splicing.

    The unquoted form (unquote x) becomes:
        (list x)

    The unquote-spliced form (unquote-splicing x) becomes
        x

    All other forms are recursively processed as by _process_syntax_quoted_form
    and are returned as:
        (list form)"""
    expanded = []

    for elem in form:
        if _is_unquote(elem):
            expanded.append(llist.l(_LIST, elem[1]))
        elif _is_unquote_splicing(elem):
            expanded.append(elem[1])
        else:
            expanded.append(llist.l(_LIST, _process_syntax_quoted_form(ctx, elem)))

    return expanded

def _expand_syntax_quote ( ctx : ReaderContext , form : IterableLispForm ) -> Iterable [ LispForm ] : expanded = [ ] for elem in form : if _is_unquote ( elem ) : expanded . append ( llist . l ( _LIST , elem [ 1 ] ) ) elif _is_unquote_splicing ( elem ) : expanded . append ( elem [ 1 ] ) else : expanded . append ( llist . l ( _LIST , _process_syntax_quoted_form ( ctx , elem ) ) ) return expanded

Expand syntax quoted forms to handle unquoting and unquote - splicing .


def _process_syntax_quoted_form(ctx: ReaderContext, form: ReaderForm) -> ReaderForm:
    """Post-process syntax quoted forms to generate forms that can be assembled
    into the correct types at runtime.

    Lists are turned into:
        (basilisp.core/seq
         (basilisp.core/concat [& rest]))

    Vectors are turned into:
        (basilisp.core/apply
         basilisp.core/vector
         (basilisp.core/concat [& rest]))

    Sets are turned into:
        (basilisp.core/apply
         basilisp.core/hash-set
         (basilisp.core/concat [& rest]))

    Maps are turned into:
        (basilisp.core/apply
         basilisp.core/hash-map
         (basilisp.core/concat [& rest]))

    The child forms (called rest above) are processed by _expand_syntax_quote.

    All other forms are passed through without modification."""
    lconcat = lambda v: llist.list(v).cons(_CONCAT)
    if _is_unquote(form):
        return form[1]  # type: ignore
    elif _is_unquote_splicing(form):
        raise SyntaxError("Cannot splice outside collection")
    elif isinstance(form, llist.List):
        return llist.l(_SEQ, lconcat(_expand_syntax_quote(ctx, form)))
    elif isinstance(form, vector.Vector):
        return llist.l(_APPLY, _VECTOR, lconcat(_expand_syntax_quote(ctx, form)))
    elif isinstance(form, lset.Set):
        return llist.l(_APPLY, _HASH_SET, lconcat(_expand_syntax_quote(ctx, form)))
    elif isinstance(form, lmap.Map):
        flat_kvs = seq(form.items()).flatten().to_list()
        return llist.l(_APPLY, _HASH_MAP, lconcat(_expand_syntax_quote(ctx, flat_kvs)))
    elif isinstance(form, symbol.Symbol):
        if form.ns is None and form.name.endswith("#"):
            try:
                return llist.l(_QUOTE, ctx.gensym_env[form.name])
            except KeyError:
                genned = symbol.symbol(langutil.genname(form.name[:-1])).with_meta(
                    form.meta
                )
                ctx.gensym_env[form.name] = genned
                return llist.l(_QUOTE, genned)
        return llist.l(_QUOTE, form)
    else:
        return form

def
_process_syntax_quoted_form
(
ctx
:
ReaderContext
,
form
:
ReaderForm
)
->
ReaderForm
:
lconcat
=
lambda
v
:
llist
.
list
(
v
)
.
cons
(
_CONCAT
)
if
_is_unquote
(
form
)
:
return
form
[
1
]
# type: ignore
elif
_is_unquote_splicing
(
form
)
:
raise
SyntaxError
(
"Cannot splice outside collection"
)
elif
isinstance
(
form
,
llist
.
List
)
:
return
llist
.
l
(
_SEQ
,
lconcat
(
_expand_syntax_quote
(
ctx
,
form
)
)
)
elif
isinstance
(
form
,
vector
.
Vector
)
:
return
llist
.
l
(
_APPLY
,
_VECTOR
,
lconcat
(
_expand_syntax_quote
(
ctx
,
form
)
)
)
elif
isinstance
(
form
,
lset
.
Set
)
:
return
llist
.
l
(
_APPLY
,
_HASH_SET
,
lconcat
(
_expand_syntax_quote
(
ctx
,
form
)
)
)
elif
isinstance
(
form
,
lmap
.
Map
)
:
flat_kvs
=
seq
(
form
.
items
(
)
)
.
flatten
(
)
.
to_list
(
)
return
llist
.
l
(
_APPLY
,
_HASH_MAP
,
lconcat
(
_expand_syntax_quote
(
ctx
,
flat_kvs
)
)
)
elif
isinstance
(
form
,
symbol
.
Symbol
)
:
if
form
.
ns
is
and
form
.
name
.
endswith
(
"#"
)
:
try
:
return
llist
.
l
(
_QUOTE
,
ctx
.
gensym_env
[
form
.
name
]
)
except
KeyError
:
genned
=
symbol
.
symbol
(
langutil
.
genname
(
form
.
name
[
:
1
]
)
)
.
with_meta
(
form
.
meta
)
ctx
.
gensym_env
[
form
.
name
]
=
genned
return
llist
.
l
(
_QUOTE
,
genned
)
return
llist
.
l
(
_QUOTE
,
form
)
else
:
return
form

Post - process syntax quoted forms to generate forms that can be assembled into the correct types at runtime .


def _read_syntax_quoted(ctx: ReaderContext) -> ReaderForm:
    """Read a syntax-quote and set the syntax-quoting state in the reader."""
    start = ctx.reader.advance()
    assert start == "`"

    with ctx.syntax_quoted():
        return _process_syntax_quoted_form(ctx, _read_next_consuming_comment(ctx))

def _read_syntax_quoted ( ctx : ReaderContext ) -> ReaderForm : start = ctx . reader . advance ( ) assert start == "`" with ctx . syntax_quoted ( ) : return _process_syntax_quoted_form ( ctx , _read_next_consuming_comment ( ctx ) )

Read a syntax - quote and set the syntax - quoting state in the reader .


def _read_unquote(ctx: ReaderContext) -> LispForm:
    """Read an unquoted form and handle any special logic of unquoting.

    Unquoted forms can take two, well... forms:

      `~form` is read as `(unquote form)` and any nested forms are read
      literally and passed along to the compiler untouched.

      `~@form` is read as `(unquote-splicing form)` which tells the compiler
      to splice in the contents of a sequential form such as a list or
      vector into the final compiled form. This helps macro writers create
      longer forms such as function calls, function bodies, or data structures
      with the contents of another collection they have."""
    start = ctx.reader.advance()
    assert start == "~"

    with ctx.unquoted():
        next_char = ctx.reader.peek()
        if next_char == "@":
            ctx.reader.advance()
            next_form = _read_next_consuming_comment(ctx)
            return llist.l(_UNQUOTE_SPLICING, next_form)
        else:
            next_form = _read_next_consuming_comment(ctx)
            return llist.l(_UNQUOTE, next_form)

def _read_unquote ( ctx : ReaderContext ) -> LispForm : start = ctx . reader . advance ( ) assert start == "~" with ctx . unquoted ( ) : next_char = ctx . reader . peek ( ) if next_char == "@" : ctx . reader . advance ( ) next_form = _read_next_consuming_comment ( ctx ) return llist . l ( _UNQUOTE_SPLICING , next_form ) else : next_form = _read_next_consuming_comment ( ctx ) return llist . l ( _UNQUOTE , next_form )

Read an unquoted form and handle any special logic of unquoting .


def _read_deref(ctx: ReaderContext) -> LispForm:
    """Read a derefed form from the input stream."""
    start = ctx.reader.advance()
    assert start == "@"
    next_form = _read_next_consuming_comment(ctx)
    return llist.l(_DEREF, next_form)

def _read_deref ( ctx : ReaderContext ) -> LispForm : start = ctx . reader . advance ( ) assert start == "@" next_form = _read_next_consuming_comment ( ctx ) return llist . l ( _DEREF , next_form )

Read a derefed form from the input stream .


def _read_character(ctx: ReaderContext) -> str:
    """Read a character literal from the input stream.

    Character literals may appear as:
      - \\a \\b \\c etc will yield 'a', 'b', and 'c' respectively

      - \\newline, \\space, \\tab, \\formfeed, \\backspace, \\return yield
        the named characters

      - \\uXXXX yield the unicode digit corresponding to the code
        point named by the hex digits XXXX"""
    start = ctx.reader.advance()
    assert start == "\\"

    s: List[str] = []
    reader = ctx.reader
    token = reader.peek()
    while True:
        if token == "" or whitespace_chars.match(token):
            break
        if not alphanumeric_chars.match(token):
            break
        s.append(token)
        token = reader.next_token()

    char = "".join(s)
    special = _SPECIAL_CHARS.get(char, None)
    if special is not None:
        return special

    match = unicode_char.match(char)
    if match is not None:
        try:
            return chr(int(f"0x{match.group(1)}", 16))
        except (ValueError, OverflowError):
            raise SyntaxError(f"Unsupported character \\u{char}") from None

    if len(char) > 1:
        raise SyntaxError(f"Unsupported character \\{char}")

    return char

def
_read_character
(
ctx
:
ReaderContext
)
->
str
:
start
=
ctx
.
reader
.
advance
(
)
assert
start
==
"\\"
s
:
List
[
str
]
=
[
]
reader
=
ctx
.
reader
token
=
reader
.
peek
(
)
while
True
:
if
token
==
""
or
whitespace_chars
.
match
(
token
)
:
break
if
not
alphanumeric_chars
.
match
(
token
)
:
break
s
.
append
(
token
)
token
=
reader
.
next_token
(
)
char
=
""
.
join
(
s
)
special
=
_SPECIAL_CHARS
.
get
(
char
,
)
if
special
is
not
:
return
special
match
=
unicode_char
.
match
(
char
)
if
match
is
not
:
try
:
return
chr
(
int
(
f"0x{match.group(1)}"
,
16
)
)
except
(
ValueError
,
OverflowError
)
:
raise
SyntaxError
(
f"Unsupported character \\u{char}"
)
from
if
len
(
char
)
>
1
:
raise
SyntaxError
(
f"Unsupported character \\{char}"
)
return
char

Read a character literal from the input stream .


def _read_regex(ctx: ReaderContext) -> Pattern:
    """Read a regex reader macro from the input stream."""
    s = _read_str(ctx, allow_arbitrary_escapes=True)
    try:
        return langutil.regex_from_str(s)
    except re.error:
        raise SyntaxError(f"Unrecognized regex pattern syntax: {s}")

Read a regex reader macro from the input stream .


def _read_reader_macro(ctx: ReaderContext) -> LispReaderForm:
    """Return a data structure evaluated as a reader
    macro from the input stream."""
    start = ctx.reader.advance()
    assert start == "#"
    token = ctx.reader.peek()
    if token == "{":
        return _read_set(ctx)
    elif token == "(":
        return _read_function(ctx)
    elif token == "'":
        ctx.reader.advance()
        s = _read_sym(ctx)
        return llist.l(_VAR, s)
    elif token == '"':
        return _read_regex(ctx)
    elif token == "_":
        ctx.reader.advance()
        _read_next(ctx)  # Ignore the entire next form
        return COMMENT
    elif ns_name_chars.match(token):
        s = _read_sym(ctx)
        assert isinstance(s, symbol.Symbol)
        v = _read_next_consuming_comment(ctx)
        if s in ctx.data_readers:
            f = ctx.data_readers[s]
            return f(v)
        else:
            raise SyntaxError(f"No data reader found for tag #{s}")

    raise SyntaxError(f"Unexpected token '{token}' in reader macro")

Return a data structure evaluated as a reader macro from the input stream .


def _read_next_consuming_comment(ctx: ReaderContext) -> ReaderForm:
    """Read the next full form from the input stream, consuming any
    reader comments completely."""
    while True:
        v = _read_next(ctx)
        if v is ctx.eof:
            return ctx.eof
        if v is COMMENT or isinstance(v, Comment):
            continue
        return v

def _read_next_consuming_comment ( ctx : ReaderContext ) -> ReaderForm : while True : v = _read_next ( ctx ) if v is ctx . eof : return ctx . eof if v is COMMENT or isinstance ( v , Comment ) : continue return v

Read the next full form from the input stream consuming any reader comments completely .


def _read_next(ctx: ReaderContext) -> LispReaderForm:  # noqa: C901
    """Read the next full form from the input stream."""
    reader = ctx.reader
    token = reader.peek()
    if token == "(":
        return _read_list(ctx)
    elif token == "[":
        return _read_vector(ctx)
    elif token == "{":
        return _read_map(ctx)
    elif begin_num_chars.match(token):
        return _read_num(ctx)
    elif whitespace_chars.match(token):
        reader.next_token()
        return _read_next(ctx)
    elif token == ":":
        return _read_kw(ctx)
    elif token == '"':
        return _read_str(ctx)
    elif token == "'":
        return _read_quoted(ctx)
    elif token == "\\":
        return _read_character(ctx)
    elif ns_name_chars.match(token):
        return _read_sym(ctx)
    elif token == "#":
        return _read_reader_macro(ctx)
    elif token == "^":
        return _read_meta(ctx)  # type: ignore
    elif token == ";":
        return _read_comment(ctx)
    elif token == "`":
        return _read_syntax_quoted(ctx)
    elif token == "~":
        return _read_unquote(ctx)
    elif token == "@":
        return _read_deref(ctx)
    elif token == "":
        return ctx.eof
    else:
        raise SyntaxError("Unexpected token '{token}'".format(token=token))

def
_read_next
(
ctx
:
ReaderContext
)
->
LispReaderForm
:
# noqa: C901
reader
=
ctx
.
reader
token
=
reader
.
peek
(
)
if
token
==
"("
:
return
_read_list
(
ctx
)
elif
token
==
"["
:
return
_read_vector
(
ctx
)
elif
token
==
"{"
:
return
_read_map
(
ctx
)
elif
begin_num_chars
.
match
(
token
)
:
return
_read_num
(
ctx
)
elif
whitespace_chars
.
match
(
token
)
:
reader
.
next_token
(
)
return
_read_next
(
ctx
)
elif
token
==
":"
:
return
_read_kw
(
ctx
)
elif
token
==
'"'
:
return
_read_str
(
ctx
)
elif
token
==
"'"
:
return
_read_quoted
(
ctx
)
elif
token
==
"\\"
:
return
_read_character
(
ctx
)
elif
ns_name_chars
.
match
(
token
)
:
return
_read_sym
(
ctx
)
elif
token
==
"#"
:
return
_read_reader_macro
(
ctx
)
elif
token
==
"^"
:
return
_read_meta
(
ctx
)
# type: ignore
elif
token
==
";"
:
return
_read_comment
(
ctx
)
elif
token
==
"`"
:
return
_read_syntax_quoted
(
ctx
)
elif
token
==
"~"
:
return
_read_unquote
(
ctx
)
elif
token
==
"@"
:
return
_read_deref
(
ctx
)
elif
token
==
""
:
return
ctx
.
eof
else
:
raise
SyntaxError
(
"Unexpected token '{token}'"
.
format
(
token
=
token
)
)

Read the next full form from the input stream .


def read(
    stream,
    resolver: Resolver = None,
    data_readers: DataReaders = None,
    eof: Any = EOF,
    is_eof_error: bool = False,
) -> Iterable[ReaderForm]:
    """Read the contents of a stream as a Lisp expression.

    Callers may optionally specify a namespace resolver, which will be used
    to adjudicate the fully-qualified name of symbols appearing inside of
    a syntax quote.

    Callers may optionally specify a map of custom data readers that will
    be used to resolve values in reader macros. Data reader tags specified
    by callers must be namespaced symbols; non-namespaced symbols are
    reserved by the reader. Data reader functions must be functions taking
    one argument and returning a value.

    The caller is responsible for closing the input stream."""
    reader = StreamReader(stream)
    ctx = ReaderContext(reader, resolver=resolver, data_readers=data_readers, eof=eof)
    while True:
        expr = _read_next(ctx)
        if expr is ctx.eof:
            if is_eof_error:
                raise EOFError
            return
        if expr is COMMENT or isinstance(expr, Comment):
            continue
        yield expr

def read ( stream , resolver : Resolver = None , data_readers : DataReaders = None , eof : Any = EOF , is_eof_error : bool = False , ) -> Iterable [ ReaderForm ] : reader = StreamReader ( stream ) ctx = ReaderContext ( reader , resolver = resolver , data_readers = data_readers , eof = eof ) while True : expr = _read_next ( ctx ) if expr is ctx . eof : if is_eof_error : raise EOFError return if expr is COMMENT or isinstance ( expr , Comment ) : continue yield expr

Read the contents of a stream as a Lisp expression .


def read_str(
    s: str,
    resolver: Resolver = None,
    data_readers: DataReaders = None,
    eof: Any = None,
    is_eof_error: bool = False,
) -> Iterable[ReaderForm]:
    """Read the contents of a string as a Lisp expression.

    Keyword arguments to this function have the same meanings as those of
    basilisp.lang.reader.read."""
    with io.StringIO(s) as buf:
        yield from read(
            buf,
            resolver=resolver,
            data_readers=data_readers,
            eof=eof,
            is_eof_error=is_eof_error,
        )

def read_str ( s : str , resolver : Resolver = None , data_readers : DataReaders = None , eof : Any = None , is_eof_error : bool = False , ) -> Iterable [ ReaderForm ] : with io . StringIO ( s ) as buf : yield from read ( buf , resolver = resolver , data_readers = data_readers , eof = eof , is_eof_error = is_eof_error , )

Read the contents of a string as a Lisp expression .


def read_file(
    filename: str,
    resolver: Resolver = None,
    data_readers: DataReaders = None,
    eof: Any = None,
    is_eof_error: bool = False,
) -> Iterable[ReaderForm]:
    """Read the contents of a file as a Lisp expression.

    Keyword arguments to this function have the same meanings as those of
    basilisp.lang.reader.read."""
    with open(filename) as f:
        yield from read(
            f,
            resolver=resolver,
            data_readers=data_readers,
            eof=eof,
            is_eof_error=is_eof_error,
        )

def read_file ( filename : str , resolver : Resolver = None , data_readers : DataReaders = None , eof : Any = None , is_eof_error : bool = False , ) -> Iterable [ ReaderForm ] : with open ( filename ) as f : yield from read ( f , resolver = resolver , data_readers = data_readers , eof = eof , is_eof_error = is_eof_error , )

Read the contents of a file as a Lisp expression .


def _update_loc(self, c):
        """Update the internal line and column buffers after a new character
        is added.

        The column number is set to 0, so the first character on the next line
        is column number 1."""
        if newline_chars.match(c):
            self._col.append(0)
            self._line.append(self._line[-1] + 1)
        else:
            self._col.append(self._col[-1] + 1)
            self._line.append(self._line[-1])

def _update_loc ( self , c ) : if newline_chars . match ( c ) : self . _col . append ( 0 ) self . _line . append ( self . _line [ - 1 ] + 1 ) else : self . _col . append ( self . _col [ - 1 ] + 1 ) self . _line . append ( self . _line [ - 1 ] )

Update the internal line and column buffers after a new character is added .


def pushback(self) -> None:
        """Push one character back onto the stream, allowing it to be
        read again."""
        if abs(self._idx - 1) > self._pushback_depth:
            raise IndexError("Exceeded pushback depth")
        self._idx -= 1

def
pushback
(
self
)
->
:
if
abs
(
self
.
_idx
1
)
>
self
.
_pushback_depth
:
raise
IndexError
(
"Exceeded pushback depth"
)
self
.
_idx
-=
1

Push one character back onto the stream allowing it to be read again .


def next_token(self) -> str:
        """Advance the stream forward by one character and return the
        next token in the stream."""
        if self._idx < StreamReader.DEFAULT_INDEX:
            self._idx += 1
        else:
            c = self._stream.read(1)
            self._update_loc(c)
            self._buffer.append(c)
        return self.peek()

def next_token ( self ) -> str : if self . _idx < StreamReader . DEFAULT_INDEX : self . _idx += 1 else : c = self . _stream . read ( 1 ) self . _update_loc ( c ) self . _buffer . append ( c ) return self . peek ( )

Advance the stream forward by one character and return the next token in the stream .


def _basilisp_bytecode(
    mtime: int, source_size: int, code: List[types.CodeType]
) -> bytes:
    """Return the bytes for a Basilisp bytecode cache file."""
    data = bytearray(MAGIC_NUMBER)
    data.extend(_w_long(mtime))
    data.extend(_w_long(source_size))
    data.extend(marshal.dumps(code))  # type: ignore
    return data

def
_basilisp_bytecode
(
mtime
:
int
,
source_size
:
int
,
code
:
List
[
types
.
CodeType
]
)
->
bytes
:
data
=
bytearray
(
MAGIC_NUMBER
)
data
.
extend
(
_w_long
(
mtime
)
)
data
.
extend
(
_w_long
(
source_size
)
)
data
.
extend
(
marshal
.
dumps
(
code
)
)
# type: ignore
return
data

Return the bytes for a Basilisp bytecode cache file .


def _get_basilisp_bytecode(
    fullname: str, mtime: int, source_size: int, cache_data: bytes
) -> List[types.CodeType]:
    """Unmarshal the bytes from a Basilisp bytecode cache file, validating the
    file header prior to returning. If the file header does not match, throw
    an exception."""
    exc_details = {"name": fullname}
    magic = cache_data[:4]
    raw_timestamp = cache_data[4:8]
    raw_size = cache_data[8:12]
    if magic != MAGIC_NUMBER:
        message = (
            f"Incorrect magic number ({magic}) in {fullname}; expected {MAGIC_NUMBER}"
        )
        logger.debug(message)
        raise ImportError(message, **exc_details)  # type: ignore
    elif len(raw_timestamp) != 4:
        message = f"Reached EOF while reading timestamp in {fullname}"
        logger.debug(message)
        raise EOFError(message)
    elif _r_long(raw_timestamp) != mtime:
        message = f"Non-matching timestamp ({_r_long(raw_timestamp)}) in {fullname} bytecode cache; expected {mtime}"
        logger.debug(message)
        raise ImportError(message, **exc_details)  # type: ignore
    elif len(raw_size) != 4:
        message = f"Reached EOF while reading size of source in {fullname}"
        logger.debug(message)
        raise EOFError(message)
    elif _r_long(raw_size) != source_size:
        message = f"Non-matching filesize ({_r_long(raw_size)}) in {fullname} bytecode cache; expected {source_size}"
        logger.debug(message)
        raise ImportError(message, **exc_details)  # type: ignore

    return marshal.loads(cache_data[12:])

Unmarshal the bytes from a Basilisp bytecode cache file validating the file header prior to returning . If the file header does not match throw an exception .


def _cache_from_source(path: str) -> str:
    """Return the path to the cached file for the given path. The original path
    does not have to exist."""
    cache_path, cache_file = os.path.split(importlib.util.cache_from_source(path))
    filename, _ = os.path.splitext(cache_file)
    return os.path.join(cache_path, filename + ".lpyc")

def _cache_from_source ( path : str ) -> str : cache_path , cache_file = os . path . split ( importlib . util . cache_from_source ( path ) ) filename , _ = os . path . splitext ( cache_file ) return os . path . join ( cache_path , filename + ".lpyc" )

Return the path to the cached file for the given path . The original path does not have to exist .


def hook_imports():
    """Hook into Python's import machinery with a custom Basilisp code
    importer.

    Once this is called, Basilisp code may be called from within Python code
    using standard `import module.submodule` syntax."""
    if any([isinstance(o, BasilispImporter) for o in sys.meta_path]):
        return
    sys.meta_path.insert(
        0, BasilispImporter()  # pylint:disable=abstract-class-instantiated
    )

Hook into Python s import machinery with a custom Basilisp code importer .


def find_spec(
        self,
        fullname: str,
        path,  # Optional[List[str]] # MyPy complains this is incompatible with supertype
        target: types.ModuleType = None,
    ) -> Optional[importlib.machinery.ModuleSpec]:
        """Find the ModuleSpec for the specified Basilisp module.

        Returns None if the module is not a Basilisp module to allow import processing to continue."""
        package_components = fullname.split(".")
        if path is None:
            path = sys.path
            module_name = package_components
        else:
            module_name = [package_components[-1]]

        for entry in path:
            filenames = [
                f"{os.path.join(entry, *module_name, '__init__')}.lpy",
                f"{os.path.join(entry, *module_name)}.lpy",
            ]
            for filename in filenames:
                if os.path.exists(filename):
                    state = {
                        "fullname": fullname,
                        "filename": filename,
                        "path": entry,
                        "target": target,
                        "cache_filename": _cache_from_source(filename),
                    }
                    logger.debug(
                        f"Found potential Basilisp module '{fullname}' in file '{filename}'"
                    )
                    return importlib.machinery.ModuleSpec(
                        fullname, self, origin=filename, loader_state=state
                    )
        return None

Find the ModuleSpec for the specified Basilisp module .


def _exec_cached_module(
        self,
        fullname: str,
        loader_state: Mapping[str, str],
        path_stats: Mapping[str, int],
        module: types.ModuleType,
    ):
        """Load and execute a cached Basilisp module."""
        filename = loader_state["filename"]
        cache_filename = loader_state["cache_filename"]

        with timed(
            lambda duration: logger.debug(
                f"Loaded cached Basilisp module '{fullname}' in {duration / 1000000}ms"
            )
        ):
            logger.debug(f"Checking for cached Basilisp module '{fullname}''")
            cache_data = self.get_data(cache_filename)
            cached_code = _get_basilisp_bytecode(
                fullname, path_stats["mtime"], path_stats["size"], cache_data
            )
            compiler.compile_bytecode(
                cached_code,
                compiler.GeneratorContext(filename=filename),
                compiler.PythonASTOptimizer(),
                module,
            )

Load and execute a cached Basilisp module .


def _exec_module(
        self,
        fullname: str,
        loader_state: Mapping[str, str],
        path_stats: Mapping[str, int],
        module: types.ModuleType,
    ):
        """Load and execute a non-cached Basilisp module."""
        filename = loader_state["filename"]
        cache_filename = loader_state["cache_filename"]

        with timed(
            lambda duration: logger.debug(
                f"Loaded Basilisp module '{fullname}' in {duration / 1000000}ms"
            )
        ):
            # During compilation, bytecode objects are added to the list via the closure
            # add_bytecode below, which is passed to the compiler. The collected bytecodes
            # will be used to generate an .lpyc file for caching the compiled file.
            all_bytecode = []

            def add_bytecode(bytecode: types.CodeType):
                all_bytecode.append(bytecode)

            logger.debug(f"Reading and compiling Basilisp module '{fullname}'")
            forms = reader.read_file(filename, resolver=runtime.resolve_alias)
            compiler.compile_module(  # pylint: disable=unexpected-keyword-arg
                forms,
                compiler.CompilerContext(filename=filename),
                module,
                collect_bytecode=add_bytecode,
            )

        # Cache the bytecode that was collected through the compilation run.
        cache_file_bytes = _basilisp_bytecode(
            path_stats["mtime"], path_stats["size"], all_bytecode
        )
        self._cache_bytecode(filename, cache_filename, cache_file_bytes)

Load and execute a non - cached Basilisp module .


def exec_module(self, module):
        """Compile the Basilisp module into Python code.

        Basilisp is fundamentally a form-at-a-time compilation, meaning that
        each form in a module may require code compiled from an earlier form, so
        we incrementally compile a Python module by evaluating a single top-level
        form at a time and inserting the resulting AST nodes into the Pyton module."""
        fullname = module.__name__
        cached = self._cache[fullname]
        cached["module"] = module
        spec = cached["spec"]
        filename = spec.loader_state["filename"]
        path_stats = self.path_stats(filename)

        # During the bootstrapping process, the 'basilisp.core namespace is created with
        # a blank module. If we do not replace the module here with the module we are
        # generating, then we will not be able to use advanced compilation features such
        # as direct Python variable access to functions and other def'ed values.
        ns_name = demunge(fullname)
        ns: runtime.Namespace = runtime.set_current_ns(ns_name).value
        ns.module = module

        # Check if a valid, cached version of this Basilisp namespace exists and, if so,
        # load it and bypass the expensive compilation process below.
        if os.getenv(_NO_CACHE_ENVVAR, None) == "true":
            self._exec_module(fullname, spec.loader_state, path_stats, module)
        else:
            try:
                self._exec_cached_module(
                    fullname, spec.loader_state, path_stats, module
                )
            except (EOFError, ImportError, IOError, OSError) as e:
                logger.debug(f"Failed to load cached Basilisp module: {e}")
                self._exec_module(fullname, spec.loader_state, path_stats, module)

        # Because we want to (by default) add 'basilisp.core into every namespace by default,
        # we want to make sure we don't try to add 'basilisp.core into itself, causing a
        # circular import error.
        #
        # Later on, we can probably remove this and just use the 'ns macro to auto-refer
        # all 'basilisp.core values into the current namespace.
        runtime.Namespace.add_default_import(ns_name)

Compile the Basilisp module into Python code .


def symbol(name: str, ns: Optional[str] = None, meta=None) -> Symbol:
    """Create a new symbol."""
    return Symbol(name, ns=ns, meta=meta)

def symbol ( name : str , ns : Optional [ str ] = None , meta = None ) -> Symbol : return Symbol ( name , ns = ns , meta = meta )


def complete(
    text: str, kw_cache: atom.Atom["PMap[int, Keyword]"] = __INTERN
) -> Iterable[str]:
    """Return an iterable of possible completions for the given text."""
    assert text.startswith(":")
    interns = kw_cache.deref()
    text = text[1:]

    if "/" in text:
        prefix, suffix = text.split("/", maxsplit=1)
        results = filter(
            lambda kw: (kw.ns is not None and kw.ns == prefix)
            and kw.name.startswith(suffix),
            interns.itervalues(),
        )
    else:
        results = filter(
            lambda kw: kw.name.startswith(text)
            or (kw.ns is not None and kw.ns.startswith(text)),
            interns.itervalues(),
        )

    return map(str, results)

def
complete
(
text
:
str
,
kw_cache
:
atom
.
Atom
[
"PMap[int, Keyword]"
]
=
__INTERN
)
->
Iterable
[
str
]
:
assert
text
.
startswith
(
":"
)
interns
=
kw_cache
.
deref
(
)
text
=
text
[
1
:
]
if
"/"
in
text
:
prefix
,
suffix
=
text
.
split
(
"/"
,
maxsplit
=
1
)
results
=
filter
(
lambda
kw
:
(
kw
.
ns
is
not
and
kw
.
ns
==
prefix
)
and
kw
.
name
.
startswith
(
suffix
)
,
interns
.
itervalues
(
)
,
)
else
:
results
=
filter
(
lambda
kw
:
kw
.
name
.
startswith
(
text
)
or
(
kw
.
ns
is
not
and
kw
.
ns
.
startswith
(
text
)
)
,
interns
.
itervalues
(
)
,
)
return
map
(
str
,
results
)

Return an iterable of possible completions for the given text .


def __get_or_create(
    kw_cache: "PMap[int, Keyword]", h: int, name: str, ns: Optional[str]
) -> PMap:
    """Private swap function used to either get the interned keyword
    instance from the input string."""
    if h in kw_cache:
        return kw_cache
    kw = Keyword(name, ns=ns)
    return kw_cache.set(h, kw)

def
__get_or_create
(
kw_cache
:
"PMap[int, Keyword]"
,
h
:
int
,
name
:
str
,
ns
:
Optional
[
str
]
)
->
PMap
:
if
h
in
kw_cache
:
return
kw_cache
kw
=
Keyword
(
name
,
ns
=
ns
)
return
kw_cache
.
set
(
h
,
kw
)

Private swap function used to either get the interned keyword instance from the input string .


def keyword(
    name: str,
    ns: Optional[str] = None,
    kw_cache: atom.Atom["PMap[int, Keyword]"] = __INTERN,
) -> Keyword:
    """Create a new keyword."""
    h = hash((name, ns))
    return kw_cache.swap(__get_or_create, h, name, ns)[h]

def
keyword
(
name
:
str
,
ns
:
Optional
[
str
]
=
,
kw_cache
:
atom
.
Atom
[
"PMap[int, Keyword]"
]
=
__INTERN
,
)
->
Keyword
:
h
=
hash
(
(
name
,
ns
)
)
return
kw_cache
.
swap
(
__get_or_create
,
h
,
name
,
ns
)
[
h
]


def _chain_py_ast(*genned: GeneratedPyAST,) -> Tuple[PyASTStream, PyASTStream]:
    """Chain a sequence of generated Python ASTs into a tuple of dependency nodes"""
    deps = chain.from_iterable(map(lambda n: n.dependencies, genned))
    nodes = map(lambda n: n.node, genned)
    return deps, nodes

def _chain_py_ast ( * genned : GeneratedPyAST , ) -> Tuple [ PyASTStream , PyASTStream ] : deps = chain . from_iterable ( map ( lambda n : n . dependencies , genned ) ) nodes = map ( lambda n : n . node , genned ) return deps , nodes


def _load_attr(name: str, ctx: ast.AST = ast.Load()) -> ast.Attribute:
    """Generate recursive Python Attribute AST nodes for resolving nested
    names."""
    attrs = name.split(".")

    def attr_node(node, idx):
        if idx >= len(attrs):
            node.ctx = ctx
            return node
        return attr_node(
            ast.Attribute(value=node, attr=attrs[idx], ctx=ast.Load()), idx + 1
        )

    return attr_node(ast.Name(id=attrs[0], ctx=ast.Load()), 1)

def _load_attr ( name : str , ctx : ast . AST = ast . Load ( ) ) -> ast . Attribute : attrs = name . split ( "." ) def attr_node ( node , idx ) : if idx >= len ( attrs ) : node . ctx = ctx return node return attr_node ( ast . Attribute ( value = node , attr = attrs [ idx ] , ctx = ast . Load ( ) ) , idx + 1 ) return attr_node ( ast . Name ( id = attrs [ 0 ] , ctx = ast . Load ( ) ) , 1 )

Generate recursive Python Attribute AST nodes for resolving nested names .


def _simple_ast_generator(gen_ast):
    """Wrap simpler AST generators to return a GeneratedPyAST."""

    @wraps(gen_ast)
    def wrapped_ast_generator(ctx: GeneratorContext, form: LispForm) -> GeneratedPyAST:
        return GeneratedPyAST(node=gen_ast(ctx, form))

    return wrapped_ast_generator

def _simple_ast_generator ( gen_ast ) : @ wraps ( gen_ast ) def wrapped_ast_generator ( ctx : GeneratorContext , form : LispForm ) -> GeneratedPyAST : return GeneratedPyAST ( node = gen_ast ( ctx , form ) ) return wrapped_ast_generator

Wrap simpler AST generators to return a GeneratedPyAST .


def _collection_ast(
    ctx: GeneratorContext, form: Iterable[Node]
) -> Tuple[PyASTStream, PyASTStream]:
    """Turn a collection of Lisp forms into Python AST nodes."""
    return _chain_py_ast(*map(partial(gen_py_ast, ctx), form))

def _collection_ast ( ctx : GeneratorContext , form : Iterable [ Node ] ) -> Tuple [ PyASTStream , PyASTStream ] : return _chain_py_ast ( * map ( partial ( gen_py_ast , ctx ) , form ) )

Turn a collection of Lisp forms into Python AST nodes .


def _ast_with_loc(
    py_ast: GeneratedPyAST, env: NodeEnv, include_dependencies: bool = False
) -> GeneratedPyAST:
    """Hydrate Generated Python AST nodes with line numbers and column offsets
    if they exist in the node environment."""
    if env.line is not None:
        py_ast.node.lineno = env.line

        if include_dependencies:
            for dep in py_ast.dependencies:
                dep.lineno = env.line

    if env.col is not None:
        py_ast.node.col_offset = env.col

        if include_dependencies:
            for dep in py_ast.dependencies:
                dep.col_offset = env.col

    return py_ast

def _ast_with_loc ( py_ast : GeneratedPyAST , env : NodeEnv , include_dependencies : bool = False ) -> GeneratedPyAST : if env . line is not None : py_ast . node . lineno = env . line if include_dependencies : for dep in py_ast . dependencies : dep . lineno = env . line if env . col is not None : py_ast . node . col_offset = env . col if include_dependencies : for dep in py_ast . dependencies : dep . col_offset = env . col return py_ast

Hydrate Generated Python AST nodes with line numbers and column offsets if they exist in the node environment .


def _with_ast_loc(f):
    """Wrap a generator function in a decorator to supply line and column
    information to the returned Python AST node. Dependency nodes will not
    be hydrated, functions whose returns need dependency nodes to be
    hydrated should use `_with_ast_loc_deps` below."""

    @wraps(f)
    def with_lineno_and_col(
        ctx: GeneratorContext, node: Node, *args, **kwargs
    ) -> GeneratedPyAST:
        py_ast = f(ctx, node, *args, **kwargs)
        return _ast_with_loc(py_ast, node.env)

    return with_lineno_and_col

def _with_ast_loc ( f ) : @ wraps ( f ) def with_lineno_and_col ( ctx : GeneratorContext , node : Node , * args , * * kwargs ) -> GeneratedPyAST : py_ast = f ( ctx , node , * args , * * kwargs ) return _ast_with_loc ( py_ast , node . env ) return with_lineno_and_col

Wrap a generator function in a decorator to supply line and column information to the returned Python AST node . Dependency nodes will not be hydrated functions whose returns need dependency nodes to be hydrated should use _with_ast_loc_deps below .


def _with_ast_loc_deps(f):
    """Wrap a generator function in a decorator to supply line and column
    information to the returned Python AST node and dependency nodes.

    Dependency nodes should likely only be included if they are new nodes
    created in the same function wrapped by this function. Otherwise, dependencies
    returned from e.g. calling `gen_py_ast` should be assumed to already have
    their location information hydrated."""

    @wraps(f)
    def with_lineno_and_col(
        ctx: GeneratorContext, node: Node, *args, **kwargs
    ) -> GeneratedPyAST:
        py_ast = f(ctx, node, *args, **kwargs)
        return _ast_with_loc(py_ast, node.env, include_dependencies=True)

    return with_lineno_and_col

def _with_ast_loc_deps ( f ) : @ wraps ( f ) def with_lineno_and_col ( ctx : GeneratorContext , node : Node , * args , * * kwargs ) -> GeneratedPyAST : py_ast = f ( ctx , node , * args , * * kwargs ) return _ast_with_loc ( py_ast , node . env , include_dependencies = True ) return with_lineno_and_col

Wrap a generator function in a decorator to supply line and column information to the returned Python AST node and dependency nodes .


def _is_dynamic(v: Var) -> bool:
    """Return True if the Var holds a value which should be compiled to a dynamic
    Var access."""
    return (
        Maybe(v.meta)
        .map(lambda m: m.get(SYM_DYNAMIC_META_KEY, None))  # type: ignore
        .or_else_get(False)
    )

def
_is_dynamic
(
v
:
Var
)
->
bool
:
return
(
Maybe
(
v
.
meta
)
.
map
(
lambda
m
:
m
.
get
(
SYM_DYNAMIC_META_KEY
,
)
)
# type: ignore
.
or_else_get
(
False
)
)

Return True if the Var holds a value which should be compiled to a dynamic Var access .


def _is_redefable(v: Var) -> bool:
    """Return True if the Var can be redefined."""
    return (
        Maybe(v.meta)
        .map(lambda m: m.get(SYM_REDEF_META_KEY, None))  # type: ignore
        .or_else_get(False)
    )

def
_is_redefable
(
v
:
Var
)
->
bool
:
return
(
Maybe
(
v
.
meta
)
.
map
(
lambda
m
:
m
.
get
(
SYM_REDEF_META_KEY
,
)
)
# type: ignore
.
or_else_get
(
False
)
)

Return True if the Var can be redefined .


def statementize(e: ast.AST) -> ast.AST:
    """Transform non-statements into ast.Expr nodes so they can
    stand alone as statements."""
    # noinspection PyPep8
    if isinstance(
        e,
        (
            ast.Assign,
            ast.AnnAssign,
            ast.AugAssign,
            ast.Expr,
            ast.Raise,
            ast.Assert,
            ast.Pass,
            ast.Import,
            ast.ImportFrom,
            ast.If,
            ast.For,
            ast.While,
            ast.Continue,
            ast.Break,
            ast.Try,
            ast.ExceptHandler,
            ast.With,
            ast.FunctionDef,
            ast.Return,
            ast.Yield,
            ast.YieldFrom,
            ast.Global,
            ast.ClassDef,
            ast.AsyncFunctionDef,
            ast.AsyncFor,
            ast.AsyncWith,
        ),
    ):
        return e
    return ast.Expr(value=e)

def
statementize
(
e
:
ast
.
AST
)
->
ast
.
AST
:
# noinspection PyPep8
if
isinstance
(
e
,
(
ast
.
Assign
,
ast
.
AnnAssign
,
ast
.
AugAssign
,
ast
.
Expr
,
ast
.
Raise
,
ast
.
Assert
,
ast
.
Pass
,
ast
.
Import
,
ast
.
ImportFrom
,
ast
.
If
,
ast
.
For
,
ast
.
While
,
ast
.
Continue
,
ast
.
Break
,
ast
.
Try
,
ast
.
ExceptHandler
,
ast
.
With
,
ast
.
FunctionDef
,
ast
.
Return
,
ast
.
Yield
,
ast
.
YieldFrom
,
ast
.
Global
,
ast
.
ClassDef
,
ast
.
AsyncFunctionDef
,
ast
.
AsyncFor
,
ast
.
AsyncWith
,
)
,
)
:
return
e
return
ast
.
Expr
(
value
=
e
)

Transform non - statements into ast . Expr nodes so they can stand alone as statements .


def expressionize(
    body: GeneratedPyAST,
    fn_name: str,
    args: Optional[Iterable[ast.arg]] = None,
    vargs: Optional[ast.arg] = None,
) -> ast.FunctionDef:
    """Given a series of expression AST nodes, create a function AST node
    with the given name that can be called and will return the result of
    the final expression in the input body nodes.

    This helps to fix the impedance mismatch of Python, which includes
    statements and expressions, and Lisps, which have only expressions.
    """
    args = Maybe(args).or_else_get([])
    body_nodes: List[ast.AST] = list(map(statementize, body.dependencies))
    body_nodes.append(ast.Return(value=body.node))

    return ast.FunctionDef(
        name=fn_name,
        args=ast.arguments(
            args=args,
            kwarg=None,
            vararg=vargs,
            kwonlyargs=[],
            defaults=[],
            kw_defaults=[],
        ),
        body=body_nodes,
        decorator_list=[],
        returns=None,
    )

def expressionize ( body : GeneratedPyAST , fn_name : str , args : Optional [ Iterable [ ast . arg ] ] = None , vargs : Optional [ ast . arg ] = None , ) -> ast . FunctionDef : args = Maybe ( args ) . or_else_get ( [ ] ) body_nodes : List [ ast . AST ] = list ( map ( statementize , body . dependencies ) ) body_nodes . append ( ast . Return ( value = body . node ) ) return ast . FunctionDef ( name = fn_name , args = ast . arguments ( args = args , kwarg = None , vararg = vargs , kwonlyargs = [ ] , defaults = [ ] , kw_defaults = [ ] , ) , body = body_nodes , decorator_list = [ ] , returns = None , )

Given a series of expression AST nodes create a function AST node with the given name that can be called and will return the result of the final expression in the input body nodes .


def __should_warn_on_redef(
    ctx: GeneratorContext, defsym: sym.Symbol, safe_name: str, def_meta: lmap.Map
) -> bool:
    """Return True if the compiler should emit a warning about this name being redefined."""
    no_warn_on_redef = def_meta.entry(SYM_NO_WARN_ON_REDEF_META_KEY, False)
    if no_warn_on_redef:
        return False
    elif safe_name in ctx.current_ns.module.__dict__:
        return True
    elif defsym in ctx.current_ns.interns:
        var = ctx.current_ns.find(defsym)
        assert var is not None, f"Var {defsym} cannot be none here"

        if var.meta is not None and var.meta.entry(SYM_REDEF_META_KEY):
            return False
        elif var.is_bound:
            return True
        else:
            return False
    else:
        return False

def
__should_warn_on_redef
(
ctx
:
GeneratorContext
,
defsym
:
sym
.
Symbol
,
safe_name
:
str
,
def_meta
:
lmap
.
Map
)
->
bool
:
no_warn_on_redef
=
def_meta
.
entry
(
SYM_NO_WARN_ON_REDEF_META_KEY
,
False
)
if
no_warn_on_redef
:
return
False
elif
safe_name
in
ctx
.
current_ns
.
module
.
__dict__
:
return
True
elif
defsym
in
ctx
.
current_ns
.
interns
:
var
=
ctx
.
current_ns
.
find
(
defsym
)
assert
var
is
not
,
f"Var {defsym} cannot be none here"
if
var
.
meta
is
not
and
var
.
meta
.
entry
(
SYM_REDEF_META_KEY
)
:
return
False
elif
var
.
is_bound
:
return
True
else
:
return
False
else
:
return
False

Return True if the compiler should emit a warning about this name being redefined .


def _do_to_py_ast(ctx: GeneratorContext, node: Do) -> GeneratedPyAST:
    """Return a Python AST Node for a `do` expression."""
    assert node.op == NodeOp.DO
    assert not node.is_body

    body_ast = GeneratedPyAST.reduce(
        *map(partial(gen_py_ast, ctx), chain(node.statements, [node.ret]))
    )

    fn_body_ast: List[ast.AST] = []
    do_result_name = genname(_DO_PREFIX)
    fn_body_ast.extend(map(statementize, body_ast.dependencies))
    fn_body_ast.append(
        ast.Assign(
            targets=[ast.Name(id=do_result_name, ctx=ast.Store())], value=body_ast.node
        )
    )

    return GeneratedPyAST(
        node=ast.Name(id=do_result_name, ctx=ast.Load()), dependencies=fn_body_ast
    )

def _do_to_py_ast ( ctx : GeneratorContext , node : Do ) -> GeneratedPyAST : assert node . op == NodeOp . DO assert not node . is_body body_ast = GeneratedPyAST . reduce ( * map ( partial ( gen_py_ast , ctx ) , chain ( node . statements , [ node . ret ] ) ) ) fn_body_ast : List [ ast . AST ] = [ ] do_result_name = genname ( _DO_PREFIX ) fn_body_ast . extend ( map ( statementize , body_ast . dependencies ) ) fn_body_ast . append ( ast . Assign ( targets = [ ast . Name ( id = do_result_name , ctx = ast . Store ( ) ) ] , value = body_ast . node ) ) return GeneratedPyAST ( node = ast . Name ( id = do_result_name , ctx = ast . Load ( ) ) , dependencies = fn_body_ast )

Return a Python AST Node for a do expression .


def __fn_name(s: Optional[str]) -> str:
    """Generate a safe Python function name from a function name symbol.
    If no symbol is provided, generate a name with a default prefix."""
    return genname("__" + munge(Maybe(s).or_else_get(_FN_PREFIX)))

def __fn_name ( s : Optional [ str ] ) -> str : return genname ( "__" + munge ( Maybe ( s ) . or_else_get ( _FN_PREFIX ) ) )

Generate a safe Python function name from a function name symbol . If no symbol is provided generate a name with a default prefix .


def __fn_args_to_py_ast(
    ctx: GeneratorContext, params: Iterable[Binding], body: Do
) -> Tuple[List[ast.arg], Optional[ast.arg], List[ast.AST]]:
    """Generate a list of Python AST nodes from function method parameters."""
    fn_args, varg = [], None
    fn_body_ast: List[ast.AST] = []
    for binding in params:
        assert binding.init is None, ":fn nodes cannot have bindint :inits"
        assert varg is None, "Must have at most one variadic arg"
        arg_name = genname(munge(binding.name))

        if not binding.is_variadic:
            fn_args.append(ast.arg(arg=arg_name, annotation=None))
            ctx.symbol_table.new_symbol(
                sym.symbol(binding.name), arg_name, LocalType.ARG
            )
        else:
            varg = ast.arg(arg=arg_name, annotation=None)
            safe_local = genname(munge(binding.name))
            fn_body_ast.append(
                ast.Assign(
                    targets=[ast.Name(id=safe_local, ctx=ast.Store())],
                    value=ast.Call(
                        func=_COLLECT_ARGS_FN_NAME,
                        args=[ast.Name(id=arg_name, ctx=ast.Load())],
                        keywords=[],
                    ),
                )
            )
            ctx.symbol_table.new_symbol(
                sym.symbol(binding.name), safe_local, LocalType.ARG
            )

    body_ast = _synthetic_do_to_py_ast(ctx, body)
    fn_body_ast.extend(map(statementize, body_ast.dependencies))
    fn_body_ast.append(ast.Return(value=body_ast.node))

    return fn_args, varg, fn_body_ast

def
__fn_args_to_py_ast
(
ctx
:
GeneratorContext
,
params
:
Iterable
[
Binding
]
,
body
:
Do
)
->
Tuple
[
List
[
ast
.
arg
]
,
Optional
[
ast
.
arg
]
,
List
[
ast
.
AST
]
]
:
fn_args
,
varg
=
[
]
,
fn_body_ast
:
List
[
ast
.
AST
]
=
[
]
for
binding
in
params
:
assert
binding
.
init
is
,
":fn nodes cannot have bindint :inits"
assert
varg
is
,
"Must have at most one variadic arg"
arg_name
=
genname
(
munge
(
binding
.
name
)
)
if
not
binding
.
is_variadic
:
fn_args
.
append
(
ast
.
arg
(
arg
=
arg_name
,
annotation
=
)
)
ctx
.
symbol_table
.
new_symbol
(
sym
.
symbol
(
binding
.
name
)
,
arg_name
,
LocalType
.
ARG
)
else
:
varg
=
ast
.
arg
(
arg
=
arg_name
,
annotation
=
)
safe_local
=
genname
(
munge
(
binding
.
name
)
)
fn_body_ast
.
append
(
ast
.
Assign
(
targets
=
[
ast
.
Name
(
id
=
safe_local
,
ctx
=
ast
.
Store
(
)
)
]
,
value
=
ast
.
Call
(
func
=
_COLLECT_ARGS_FN_NAME
,
args
=
[
ast
.
Name
(
id
=
arg_name
,
ctx
=
ast
.
Load
(
)
)
]
,
keywords
=
[
]
,
)
,
)
)
ctx
.
symbol_table
.
new_symbol
(
sym
.
symbol
(
binding
.
name
)
,
safe_local
,
LocalType
.
ARG
)
body_ast
=
_synthetic_do_to_py_ast
(
ctx
,
body
)
fn_body_ast
.
extend
(
map
(
statementize
,
body_ast
.
dependencies
)
)
fn_body_ast
.
append
(
ast
.
Return
(
value
=
body_ast
.
node
)
)
return
fn_args
,
varg
,
fn_body_ast

Generate a list of Python AST nodes from function method parameters .


def __single_arity_fn_to_py_ast(
    ctx: GeneratorContext,
    node: Fn,
    method: FnMethod,
    def_name: Optional[str] = None,
    meta_node: Optional[MetaNode] = None,
) -> GeneratedPyAST:
    """Return a Python AST node for a function with a single arity."""
    assert node.op == NodeOp.FN
    assert method.op == NodeOp.FN_METHOD

    lisp_fn_name = node.local.name if node.local is not None else None
    py_fn_name = __fn_name(lisp_fn_name) if def_name is None else munge(def_name)
    py_fn_node = ast.AsyncFunctionDef if node.is_async else ast.FunctionDef
    with ctx.new_symbol_table(py_fn_name), ctx.new_recur_point(
        method.loop_id, RecurType.FN, is_variadic=node.is_variadic
    ):
        # Allow named anonymous functions to recursively call themselves
        if lisp_fn_name is not None:
            ctx.symbol_table.new_symbol(
                sym.symbol(lisp_fn_name), py_fn_name, LocalType.FN
            )

        fn_args, varg, fn_body_ast = __fn_args_to_py_ast(
            ctx, method.params, method.body
        )
        meta_deps, meta_decorators = __fn_meta(ctx, meta_node)
        return GeneratedPyAST(
            node=ast.Name(id=py_fn_name, ctx=ast.Load()),
            dependencies=list(
                chain(
                    meta_deps,
                    [
                        py_fn_node(
                            name=py_fn_name,
                            args=ast.arguments(
                                args=fn_args,
                                kwarg=None,
                                vararg=varg,
                                kwonlyargs=[],
                                defaults=[],
                                kw_defaults=[],
                            ),
                            body=fn_body_ast,
                            decorator_list=list(
                                chain(
                                    meta_decorators,
                                    [_BASILISP_FN_FN_NAME],
                                    [_TRAMPOLINE_FN_NAME]
                                    if ctx.recur_point.has_recur
                                    else [],
                                )
                            ),
                            returns=None,
                        )
                    ],
                )
            ),
        )

Return a Python AST node for a function with a single arity .


def __multi_arity_fn_to_py_ast(  # pylint: disable=too-many-locals
    ctx: GeneratorContext,
    node: Fn,
    methods: Collection[FnMethod],
    def_name: Optional[str] = None,
    meta_node: Optional[MetaNode] = None,
) -> GeneratedPyAST:
    """Return a Python AST node for a function with multiple arities."""
    assert node.op == NodeOp.FN
    assert all([method.op == NodeOp.FN_METHOD for method in methods])

    lisp_fn_name = node.local.name if node.local is not None else None
    py_fn_name = __fn_name(lisp_fn_name) if def_name is None else munge(def_name)

    py_fn_node = ast.AsyncFunctionDef if node.is_async else ast.FunctionDef

    arity_to_name = {}
    rest_arity_name: Optional[str] = None
    fn_defs = []
    for method in methods:
        arity_name = f"{py_fn_name}__arity{'_rest' if method.is_variadic else method.fixed_arity}"
        if method.is_variadic:
            rest_arity_name = arity_name
        else:
            arity_to_name[method.fixed_arity] = arity_name

        with ctx.new_symbol_table(arity_name), ctx.new_recur_point(
            method.loop_id, RecurType.FN, is_variadic=node.is_variadic
        ):
            # Allow named anonymous functions to recursively call themselves
            if lisp_fn_name is not None:
                ctx.symbol_table.new_symbol(
                    sym.symbol(lisp_fn_name), py_fn_name, LocalType.FN
                )

            fn_args, varg, fn_body_ast = __fn_args_to_py_ast(
                ctx, method.params, method.body
            )
            fn_defs.append(
                py_fn_node(
                    name=arity_name,
                    args=ast.arguments(
                        args=fn_args,
                        kwarg=None,
                        vararg=varg,
                        kwonlyargs=[],
                        defaults=[],
                        kw_defaults=[],
                    ),
                    body=fn_body_ast,
                    decorator_list=[_TRAMPOLINE_FN_NAME]
                    if ctx.recur_point.has_recur
                    else [],
                    returns=None,
                )
            )

    dispatch_fn_ast = __multi_arity_dispatch_fn(
        ctx,
        py_fn_name,
        arity_to_name,
        default_name=rest_arity_name,
        max_fixed_arity=node.max_fixed_arity,
        meta_node=meta_node,
        is_async=node.is_async,
    )

    return GeneratedPyAST(
        node=dispatch_fn_ast.node,
        dependencies=list(chain(fn_defs, dispatch_fn_ast.dependencies)),
    )

Return a Python AST node for a function with multiple arities .


def _fn_to_py_ast(
    ctx: GeneratorContext,
    node: Fn,
    def_name: Optional[str] = None,
    meta_node: Optional[MetaNode] = None,
) -> GeneratedPyAST:
    """Return a Python AST Node for a `fn` expression."""
    assert node.op == NodeOp.FN
    if len(node.methods) == 1:
        return __single_arity_fn_to_py_ast(
            ctx, node, next(iter(node.methods)), def_name=def_name, meta_node=meta_node
        )
    else:
        return __multi_arity_fn_to_py_ast(
            ctx, node, node.methods, def_name=def_name, meta_node=meta_node
        )

def _fn_to_py_ast ( ctx : GeneratorContext , node : Fn , def_name : Optional [ str ] = None , meta_node : Optional [ MetaNode ] = None , ) -> GeneratedPyAST : assert node . op == NodeOp . FN if len ( node . methods ) == 1 : return __single_arity_fn_to_py_ast ( ctx , node , next ( iter ( node . methods ) ) , def_name = def_name , meta_node = meta_node ) else : return __multi_arity_fn_to_py_ast ( ctx , node , node . methods , def_name = def_name , meta_node = meta_node )

Return a Python AST Node for a fn expression .


def __if_body_to_py_ast(
    ctx: GeneratorContext, node: Node, result_name: str
) -> GeneratedPyAST:
    """Generate custom `if` nodes to handle `recur` bodies.

    Recur nodes can appear in the then and else expressions of `if` forms.
    Recur nodes generate Python `continue` statements, which we would otherwise
    attempt to insert directly into an expression. Python will complain if
    it finds a statement in an expression AST slot, so we special case the
    recur handling here."""
    if node.op == NodeOp.RECUR and ctx.recur_point.type == RecurType.LOOP:
        assert isinstance(node, Recur)
        return _recur_to_py_ast(ctx, node)
    elif node.op == NodeOp.DO:
        assert isinstance(node, Do)
        if_body = _synthetic_do_to_py_ast(ctx, node.assoc(is_body=True))
        return GeneratedPyAST(
            node=ast.Assign(
                targets=[ast.Name(id=result_name, ctx=ast.Store())], value=if_body.node
            ),
            dependencies=list(map(statementize, if_body.dependencies)),
        )
    else:
        py_ast = gen_py_ast(ctx, node)
        return GeneratedPyAST(
            node=ast.Assign(
                targets=[ast.Name(id=result_name, ctx=ast.Store())], value=py_ast.node
            ),
            dependencies=py_ast.dependencies,
        )

def __if_body_to_py_ast ( ctx : GeneratorContext , node : Node , result_name : str ) -> GeneratedPyAST : if node . op == NodeOp . RECUR and ctx . recur_point . type == RecurType . LOOP : assert isinstance ( node , Recur ) return _recur_to_py_ast ( ctx , node ) elif node . op == NodeOp . DO : assert isinstance ( node , Do ) if_body = _synthetic_do_to_py_ast ( ctx , node . assoc ( is_body = True ) ) return GeneratedPyAST ( node = ast . Assign ( targets = [ ast . Name ( id = result_name , ctx = ast . Store ( ) ) ] , value = if_body . node ) , dependencies = list ( map ( statementize , if_body . dependencies ) ) , ) else : py_ast = gen_py_ast ( ctx , node ) return GeneratedPyAST ( node = ast . Assign ( targets = [ ast . Name ( id = result_name , ctx = ast . Store ( ) ) ] , value = py_ast . node ) , dependencies = py_ast . dependencies , )

Generate custom if nodes to handle recur bodies .


def _if_to_py_ast(ctx: GeneratorContext, node: If) -> GeneratedPyAST:
    """Generate an intermediate if statement which assigns to a temporary
    variable, which is returned as the expression value at the end of
    evaluation.

    Every expression in Basilisp is true if it is not the literal values nil
    or false. This function compiles direct checks for the test value against
    the Python values None and False to accommodate this behavior.

    Note that the if and else bodies are switched in compilation so that we
    can perform a short-circuit or comparison, rather than exhaustively checking
    for both false and nil each time."""
    assert node.op == NodeOp.IF

    test_ast = gen_py_ast(ctx, node.test)
    result_name = genname(_IF_RESULT_PREFIX)

    then_ast = __if_body_to_py_ast(ctx, node.then, result_name)
    else_ast = __if_body_to_py_ast(ctx, node.else_, result_name)

    test_name = genname(_IF_TEST_PREFIX)
    test_assign = ast.Assign(
        targets=[ast.Name(id=test_name, ctx=ast.Store())], value=test_ast.node
    )

    ifstmt = ast.If(
        test=ast.BoolOp(
            op=ast.Or(),
            values=[
                ast.Compare(
                    left=ast.NameConstant(None),
                    ops=[ast.Is()],
                    comparators=[ast.Name(id=test_name, ctx=ast.Load())],
                ),
                ast.Compare(
                    left=ast.NameConstant(False),
                    ops=[ast.Is()],
                    comparators=[ast.Name(id=test_name, ctx=ast.Load())],
                ),
            ],
        ),
        values=[],
        body=list(map(statementize, chain(else_ast.dependencies, [else_ast.node]))),
        orelse=list(map(statementize, chain(then_ast.dependencies, [then_ast.node]))),
    )

    return GeneratedPyAST(
        node=ast.Name(id=result_name, ctx=ast.Load()),
        dependencies=list(chain(test_ast.dependencies, [test_assign, ifstmt])),
    )

def _if_to_py_ast ( ctx : GeneratorContext , node : If ) -> GeneratedPyAST : assert node . op == NodeOp . IF test_ast = gen_py_ast ( ctx , node . test ) result_name = genname ( _IF_RESULT_PREFIX ) then_ast = __if_body_to_py_ast ( ctx , node . then , result_name ) else_ast = __if_body_to_py_ast ( ctx , node . else_ , result_name ) test_name = genname ( _IF_TEST_PREFIX ) test_assign = ast . Assign ( targets = [ ast . Name ( id = test_name , ctx = ast . Store ( ) ) ] , value = test_ast . node ) ifstmt = ast . If ( test = ast . BoolOp ( op = ast . Or ( ) , values = [ ast . Compare ( left = ast . NameConstant ( None ) , ops = [ ast . Is ( ) ] , comparators = [ ast . Name ( id = test_name , ctx = ast . Load ( ) ) ] , ) , ast . Compare ( left = ast . NameConstant ( False ) , ops = [ ast . Is ( ) ] , comparators = [ ast . Name ( id = test_name , ctx = ast . Load ( ) ) ] , ) , ] , ) , values = [ ] , body = list ( map ( statementize , chain ( else_ast . dependencies , [ else_ast . node ] ) ) ) , orelse = list ( map ( statementize , chain ( then_ast . dependencies , [ then_ast . node ] ) ) ) , ) return GeneratedPyAST ( node = ast . Name ( id = result_name , ctx = ast . Load ( ) ) , dependencies = list ( chain ( test_ast . dependencies , [ test_assign , ifstmt ] ) ) , )

Generate an intermediate if statement which assigns to a temporary variable which is returned as the expression value at the end of evaluation .


def _invoke_to_py_ast(ctx: GeneratorContext, node: Invoke) -> GeneratedPyAST:
    """Return a Python AST Node for a Basilisp function invocation."""
    assert node.op == NodeOp.INVOKE

    fn_ast = gen_py_ast(ctx, node.fn)
    args_deps, args_nodes = _collection_ast(ctx, node.args)

    return GeneratedPyAST(
        node=ast.Call(func=fn_ast.node, args=list(args_nodes), keywords=[]),
        dependencies=list(chain(fn_ast.dependencies, args_deps)),
    )

def _invoke_to_py_ast ( ctx : GeneratorContext , node : Invoke ) -> GeneratedPyAST : assert node . op == NodeOp . INVOKE fn_ast = gen_py_ast ( ctx , node . fn ) args_deps , args_nodes = _collection_ast ( ctx , node . args ) return GeneratedPyAST ( node = ast . Call ( func = fn_ast . node , args = list ( args_nodes ) , keywords = [ ] ) , dependencies = list ( chain ( fn_ast . dependencies , args_deps ) ) , )

Return a Python AST Node for a Basilisp function invocation .


def _quote_to_py_ast(ctx: GeneratorContext, node: Quote) -> GeneratedPyAST:
    """Return a Python AST Node for a `quote` expression."""
    assert node.op == NodeOp.QUOTE
    return _const_node_to_py_ast(ctx, node.expr)

def _quote_to_py_ast ( ctx : GeneratorContext , node : Quote ) -> GeneratedPyAST : assert node . op == NodeOp . QUOTE return _const_node_to_py_ast ( ctx , node . expr )

Return a Python AST Node for a quote expression .


def __loop_recur_to_py_ast(ctx: GeneratorContext, node: Recur) -> GeneratedPyAST:
    """Return a Python AST node for `recur` occurring inside a `loop`."""
    assert node.op == NodeOp.RECUR

    recur_deps: List[ast.AST] = []
    recur_targets: List[ast.Name] = []
    recur_exprs: List[ast.AST] = []
    for name, expr in zip(ctx.recur_point.binding_names, node.exprs):
        expr_ast = gen_py_ast(ctx, expr)
        recur_deps.extend(expr_ast.dependencies)
        recur_targets.append(ast.Name(id=name, ctx=ast.Store()))
        recur_exprs.append(expr_ast.node)

    if len(recur_targets) == 1:
        assert len(recur_exprs) == 1
        recur_deps.append(ast.Assign(targets=recur_targets, value=recur_exprs[0]))
    else:
        recur_deps.append(
            ast.Assign(
                targets=[ast.Tuple(elts=recur_targets, ctx=ast.Store())],
                value=ast.Tuple(elts=recur_exprs, ctx=ast.Load()),
            )
        )
    recur_deps.append(ast.Continue())

    return GeneratedPyAST(node=ast.NameConstant(None), dependencies=recur_deps)

def __loop_recur_to_py_ast ( ctx : GeneratorContext , node : Recur ) -> GeneratedPyAST : assert node . op == NodeOp . RECUR recur_deps : List [ ast . AST ] = [ ] recur_targets : List [ ast . Name ] = [ ] recur_exprs : List [ ast . AST ] = [ ] for name , expr in zip ( ctx . recur_point . binding_names , node . exprs ) : expr_ast = gen_py_ast ( ctx , expr ) recur_deps . extend ( expr_ast . dependencies ) recur_targets . append ( ast . Name ( id = name , ctx = ast . Store ( ) ) ) recur_exprs . append ( expr_ast . node ) if len ( recur_targets ) == 1 : assert len ( recur_exprs ) == 1 recur_deps . append ( ast . Assign ( targets = recur_targets , value = recur_exprs [ 0 ] ) ) else : recur_deps . append ( ast . Assign ( targets = [ ast . Tuple ( elts = recur_targets , ctx = ast . Store ( ) ) ] , value = ast . Tuple ( elts = recur_exprs , ctx = ast . Load ( ) ) , ) ) recur_deps . append ( ast . Continue ( ) ) return GeneratedPyAST ( node = ast . NameConstant ( None ) , dependencies = recur_deps )

Return a Python AST node for recur occurring inside a loop .


def _recur_to_py_ast(ctx: GeneratorContext, node: Recur) -> GeneratedPyAST:
    """Return a Python AST Node for a `recur` expression.

    Note that `recur` nodes can only legally appear in two AST locations:
      (1) in :then or :else expressions in :if nodes, and
      (2) in :ret expressions in :do nodes

    As such, both of these handlers special case the recur construct, as it
    is the only case in which the code generator emits a statement rather than
    an expression."""
    assert node.op == NodeOp.RECUR
    assert ctx.recur_point is not None, "Must have set a recur point to recur"
    handle_recur = _RECUR_TYPE_HANDLER.get(ctx.recur_point.type)
    assert (
        handle_recur is not None
    ), f"No recur point handler defined for {ctx.recur_point.type}"
    ctx.recur_point.has_recur = True
    return handle_recur(ctx, node)

Return a Python AST Node for a recur expression .


def _set_bang_to_py_ast(ctx: GeneratorContext, node: SetBang) -> GeneratedPyAST:
    """Return a Python AST Node for a `set!` expression."""
    assert node.op == NodeOp.SET_BANG

    val_temp_name = genname("set_bang_val")
    val_ast = gen_py_ast(ctx, node.val)

    target = node.target
    assert isinstance(
        target, (HostField, Local, VarRef)
    ), f"invalid set! target type {type(target)}"

    if isinstance(target, HostField):
        target_ast = _interop_prop_to_py_ast(ctx, target, is_assigning=True)
    elif isinstance(target, VarRef):
        target_ast = _var_sym_to_py_ast(ctx, target, is_assigning=True)
    elif isinstance(target, Local):
        target_ast = _local_sym_to_py_ast(ctx, target, is_assigning=True)
    else:  # pragma: no cover
        raise GeneratorException(
            f"invalid set! target type {type(target)}", lisp_ast=target
        )

    return GeneratedPyAST(
        node=ast.Name(id=val_temp_name, ctx=ast.Load()),
        dependencies=list(
            chain(
                val_ast.dependencies,
                [
                    ast.Assign(
                        targets=[ast.Name(id=val_temp_name, ctx=ast.Store())],
                        value=val_ast.node,
                    )
                ],
                target_ast.dependencies,
                [ast.Assign(targets=[target_ast.node], value=val_ast.node)],
            )
        ),
    )

Return a Python AST Node for a set! expression .


def _throw_to_py_ast(ctx: GeneratorContext, node: Throw) -> GeneratedPyAST:
    """Return a Python AST Node for a `throw` expression."""
    assert node.op == NodeOp.THROW

    throw_fn = genname(_THROW_PREFIX)
    exc_ast = gen_py_ast(ctx, node.exception)
    raise_body = ast.Raise(exc=exc_ast.node, cause=None)

    return GeneratedPyAST(
        node=ast.Call(func=ast.Name(id=throw_fn, ctx=ast.Load()), args=[], keywords=[]),
        dependencies=[
            ast.FunctionDef(
                name=throw_fn,
                args=ast.arguments(
                    args=[],
                    kwarg=None,
                    vararg=None,
                    kwonlyargs=[],
                    defaults=[],
                    kw_defaults=[],
                ),
                body=list(chain(exc_ast.dependencies, [raise_body])),
                decorator_list=[],
                returns=None,
            )
        ],
    )

def _throw_to_py_ast ( ctx : GeneratorContext , node : Throw ) -> GeneratedPyAST : assert node . op == NodeOp . THROW throw_fn = genname ( _THROW_PREFIX ) exc_ast = gen_py_ast ( ctx , node . exception ) raise_body = ast . Raise ( exc = exc_ast . node , cause = None ) return GeneratedPyAST ( node = ast . Call ( func = ast . Name ( id = throw_fn , ctx = ast . Load ( ) ) , args = [ ] , keywords = [ ] ) , dependencies = [ ast . FunctionDef ( name = throw_fn , args = ast . arguments ( args = [ ] , kwarg = None , vararg = None , kwonlyargs = [ ] , defaults = [ ] , kw_defaults = [ ] , ) , body = list ( chain ( exc_ast . dependencies , [ raise_body ] ) ) , decorator_list = [ ] , returns = None , ) ] , )

Return a Python AST Node for a throw expression .


def _try_to_py_ast(ctx: GeneratorContext, node: Try) -> GeneratedPyAST:
    """Return a Python AST Node for a `try` expression."""
    assert node.op == NodeOp.TRY

    try_expr_name = genname("try_expr")

    body_ast = _synthetic_do_to_py_ast(ctx, node.body)
    catch_handlers = list(
        map(partial(__catch_to_py_ast, ctx, try_expr_name=try_expr_name), node.catches)
    )

    finallys: List[ast.AST] = []
    if node.finally_ is not None:
        finally_ast = _synthetic_do_to_py_ast(ctx, node.finally_)
        finallys.extend(map(statementize, finally_ast.dependencies))
        finallys.append(statementize(finally_ast.node))

    return GeneratedPyAST(
        node=ast.Name(id=try_expr_name, ctx=ast.Load()),
        dependencies=[
            ast.Try(
                body=list(
                    chain(
                        body_ast.dependencies,
                        [
                            ast.Assign(
                                targets=[ast.Name(id=try_expr_name, ctx=ast.Store())],
                                value=body_ast.node,
                            )
                        ],
                    )
                ),
                handlers=catch_handlers,
                orelse=[],
                finalbody=finallys,
            )
        ],
    )

def _try_to_py_ast ( ctx : GeneratorContext , node : Try ) -> GeneratedPyAST : assert node . op == NodeOp . TRY try_expr_name = genname ( "try_expr" ) body_ast = _synthetic_do_to_py_ast ( ctx , node . body ) catch_handlers = list ( map ( partial ( __catch_to_py_ast , ctx , try_expr_name = try_expr_name ) , node . catches ) ) finallys : List [ ast . AST ] = [ ] if node . finally_ is not None : finally_ast = _synthetic_do_to_py_ast ( ctx , node . finally_ ) finallys . extend ( map ( statementize , finally_ast . dependencies ) ) finallys . append ( statementize ( finally_ast . node ) ) return GeneratedPyAST ( node = ast . Name ( id = try_expr_name , ctx = ast . Load ( ) ) , dependencies = [ ast . Try ( body = list ( chain ( body_ast . dependencies , [ ast . Assign ( targets = [ ast . Name ( id = try_expr_name , ctx = ast . Store ( ) ) ] , value = body_ast . node , ) ] , ) ) , handlers = catch_handlers , orelse = [ ] , finalbody = finallys , ) ] , )

Return a Python AST Node for a try expression .


def _local_sym_to_py_ast(
    ctx: GeneratorContext, node: Local, is_assigning: bool = False
) -> GeneratedPyAST:
    """Generate a Python AST node for accessing a locally defined Python variable."""
    assert node.op == NodeOp.LOCAL

    sym_entry = ctx.symbol_table.find_symbol(sym.symbol(node.name))
    assert sym_entry is not None

    if node.local == LocalType.FIELD:
        this_entry = ctx.symbol_table.find_symbol(ctx.current_this)
        assert this_entry is not None, "Field type local must have this"

        return GeneratedPyAST(
            node=_load_attr(
                f"{this_entry.munged}.{sym_entry.munged}",
                ctx=ast.Store() if is_assigning else ast.Load(),
            )
        )
    else:
        return GeneratedPyAST(
            node=ast.Name(
                id=sym_entry.munged, ctx=ast.Store() if is_assigning else ast.Load()
            )
        )

Generate a Python AST node for accessing a locally defined Python variable .


def __var_find_to_py_ast(
    var_name: str, ns_name: str, py_var_ctx: ast.AST
) -> GeneratedPyAST:
    """Generate Var.find calls for the named symbol."""
    return GeneratedPyAST(
        node=ast.Attribute(
            value=ast.Call(
                func=_FIND_VAR_FN_NAME,
                args=[
                    ast.Call(
                        func=_NEW_SYM_FN_NAME,
                        args=[ast.Str(var_name)],
                        keywords=[ast.keyword(arg="ns", value=ast.Str(ns_name))],
                    )
                ],
                keywords=[],
            ),
            attr="value",
            ctx=py_var_ctx,
        )
    )

def __var_find_to_py_ast ( var_name : str , ns_name : str , py_var_ctx : ast . AST ) -> GeneratedPyAST : return GeneratedPyAST ( node = ast . Attribute ( value = ast . Call ( func = _FIND_VAR_FN_NAME , args = [ ast . Call ( func = _NEW_SYM_FN_NAME , args = [ ast . Str ( var_name ) ] , keywords = [ ast . keyword ( arg = "ns" , value = ast . Str ( ns_name ) ) ] , ) ] , keywords = [ ] , ) , attr = "value" , ctx = py_var_ctx , ) )

Generate Var . find calls for the named symbol .


def _var_sym_to_py_ast(
    ctx: GeneratorContext, node: VarRef, is_assigning: bool = False
) -> GeneratedPyAST:
    """Generate a Python AST node for accessing a Var.

    If the Var is marked as :dynamic or :redef or the compiler option
    USE_VAR_INDIRECTION is active, do not compile to a direct access.
    If the corresponding function name is not defined in a Python module,
    no direct variable access is possible and Var.find indirection must be
    used."""
    assert node.op == NodeOp.VAR

    var = node.var
    ns = var.ns
    ns_name = ns.name
    ns_module = ns.module
    safe_ns = munge(ns_name)
    var_name = var.name.name
    py_var_ctx = ast.Store() if is_assigning else ast.Load()

    # Return the actual var, rather than its value if requested
    if node.return_var:
        return GeneratedPyAST(
            node=ast.Call(
                func=_FIND_VAR_FN_NAME,
                args=[
                    ast.Call(
                        func=_NEW_SYM_FN_NAME,
                        args=[ast.Str(var_name)],
                        keywords=[ast.keyword(arg="ns", value=ast.Str(ns_name))],
                    )
                ],
                keywords=[],
            )
        )

    # Check if we should use Var indirection
    if ctx.use_var_indirection or _is_dynamic(var) or _is_redefable(var):
        return __var_find_to_py_ast(var_name, ns_name, py_var_ctx)

    # Otherwise, try to direct-link it like a Python variable
    # Try without allowing builtins first
    safe_name = munge(var_name)
    if safe_name not in ns_module.__dict__:
        # Try allowing builtins
        safe_name = munge(var_name, allow_builtins=True)

    if safe_name in ns_module.__dict__:
        if ns is ctx.current_ns:
            return GeneratedPyAST(node=ast.Name(id=safe_name, ctx=py_var_ctx))
        return GeneratedPyAST(node=_load_attr(f"{safe_ns}.{safe_name}", ctx=py_var_ctx))

    if ctx.warn_on_var_indirection:
        logger.warning(f"could not resolve a direct link to Var '{var_name}'")

    return __var_find_to_py_ast(var_name, ns_name, py_var_ctx)

Generate a Python AST node for accessing a Var .


def _interop_prop_to_py_ast(
    ctx: GeneratorContext, node: HostField, is_assigning: bool = False
) -> GeneratedPyAST:
    """Generate a Python AST node for Python interop property access."""
    assert node.op == NodeOp.HOST_FIELD

    target_ast = gen_py_ast(ctx, node.target)

    return GeneratedPyAST(
        node=ast.Attribute(
            value=target_ast.node,
            attr=munge(node.field),
            ctx=ast.Store() if is_assigning else ast.Load(),
        ),
        dependencies=target_ast.dependencies,
    )

def _interop_prop_to_py_ast ( ctx : GeneratorContext , node : HostField , is_assigning : bool = False ) -> GeneratedPyAST : assert node . op == NodeOp . HOST_FIELD target_ast = gen_py_ast ( ctx , node . target ) return GeneratedPyAST ( node = ast . Attribute ( value = target_ast . node , attr = munge ( node . field ) , ctx = ast . Store ( ) if is_assigning else ast . Load ( ) , ) , dependencies = target_ast . dependencies , )

Generate a Python AST node for Python interop property access .


def _maybe_class_to_py_ast(_: GeneratorContext, node: MaybeClass) -> GeneratedPyAST:
    """Generate a Python AST node for accessing a potential Python module
    variable name."""
    assert node.op == NodeOp.MAYBE_CLASS
    return GeneratedPyAST(
        node=ast.Name(
            id=Maybe(_MODULE_ALIASES.get(node.class_)).or_else_get(node.class_),
            ctx=ast.Load(),
        )
    )

def _maybe_class_to_py_ast ( _ : GeneratorContext , node : MaybeClass ) -> GeneratedPyAST : assert node . op == NodeOp . MAYBE_CLASS return GeneratedPyAST ( node = ast . Name ( id = Maybe ( _MODULE_ALIASES . get ( node . class_ ) ) . or_else_get ( node . class_ ) , ctx = ast . Load ( ) , ) )

Generate a Python AST node for accessing a potential Python module variable name .


def _maybe_host_form_to_py_ast(
    _: GeneratorContext, node: MaybeHostForm
) -> GeneratedPyAST:
    """Generate a Python AST node for accessing a potential Python module
    variable name with a namespace."""
    assert node.op == NodeOp.MAYBE_HOST_FORM
    return GeneratedPyAST(
        node=_load_attr(
            f"{Maybe(_MODULE_ALIASES.get(node.class_)).or_else_get(node.class_)}.{node.field}"
        )
    )

Generate a Python AST node for accessing a potential Python module variable name with a namespace .


def _const_val_to_py_ast(ctx: GeneratorContext, form: LispForm) -> GeneratedPyAST:
    """Generate Python AST nodes for constant Lisp forms.

    Nested values in collections for :const nodes are not parsed, so recursive
    structures need to call into this function to generate Python AST nodes for
    nested elements. For top-level :const Lisp AST nodes, see
    `_const_node_to_py_ast`."""
    handle_value = _CONST_VALUE_HANDLERS.get(type(form))
    if handle_value is None and isinstance(form, ISeq):
        handle_value = _const_seq_to_py_ast  # type: ignore
    assert handle_value is not None, "A type handler must be defined for constants"
    return handle_value(ctx, form)

Generate Python AST nodes for constant Lisp forms .


def _collection_literal_to_py_ast(
    ctx: GeneratorContext, form: Iterable[LispForm]
) -> Iterable[GeneratedPyAST]:
    """Turn a quoted collection literal of Lisp forms into Python AST nodes.

    This function can only handle constant values. It does not call back into
    the generic AST generators, so only constant values will be generated down
    this path."""
    yield from map(partial(_const_val_to_py_ast, ctx), form)

def _collection_literal_to_py_ast ( ctx : GeneratorContext , form : Iterable [ LispForm ] ) -> Iterable [ GeneratedPyAST ] : yield from map ( partial ( _const_val_to_py_ast , ctx ) , form )

Turn a quoted collection literal of Lisp forms into Python AST nodes .


def gen_py_ast(ctx: GeneratorContext, lisp_ast: Node) -> GeneratedPyAST:
    """Take a Lisp AST node as an argument and produce zero or more Python
    AST nodes.

    This is the primary entrypoint for generating AST nodes from Lisp
    syntax. It may be called recursively to compile child forms."""
    op: NodeOp = lisp_ast.op
    assert op is not None, "Lisp AST nodes must have an :op key"
    handle_node = _NODE_HANDLERS.get(op)
    assert (
        handle_node is not None
    ), f"Lisp AST nodes :op has no handler defined for op {op}"
    return handle_node(ctx, lisp_ast)

Take a Lisp AST node as an argument and produce zero or more Python AST nodes .


def _module_imports(ctx: GeneratorContext) -> Iterable[ast.Import]:
    """Generate the Python Import AST node for importing all required
    language support modules."""
    # Yield `import basilisp` so code attempting to call fully qualified
    # `basilisp.lang...` modules don't result in compiler errors
    yield ast.Import(names=[ast.alias(name="basilisp", asname=None)])
    for imp in ctx.imports:
        name = imp.key.name
        alias = _MODULE_ALIASES.get(name, None)
        yield ast.Import(names=[ast.alias(name=name, asname=alias)])

Generate the Python Import AST node for importing all required language support modules .


def _from_module_import() -> ast.ImportFrom:
    """Generate the Python From ... Import AST node for importing
    language support modules."""
    return ast.ImportFrom(
        module="basilisp.lang.runtime",
        names=[ast.alias(name="Var", asname=_VAR_ALIAS)],
        level=0,
    )

def _from_module_import ( ) -> ast . ImportFrom : return ast . ImportFrom ( module = "basilisp.lang.runtime" , names = [ ast . alias ( name = "Var" , asname = _VAR_ALIAS ) ] , level = 0 , )

Generate the Python From ... Import AST node for importing language support modules .


def _ns_var(
    py_ns_var: str = _NS_VAR, lisp_ns_var: str = LISP_NS_VAR, lisp_ns_ns: str = CORE_NS
) -> ast.Assign:
    """Assign a Python variable named `ns_var` to the value of the current
    namespace."""
    return ast.Assign(
        targets=[ast.Name(id=py_ns_var, ctx=ast.Store())],
        value=ast.Call(
            func=_FIND_VAR_FN_NAME,
            args=[
                ast.Call(
                    func=_NEW_SYM_FN_NAME,
                    args=[ast.Str(lisp_ns_var)],
                    keywords=[ast.keyword(arg="ns", value=ast.Str(lisp_ns_ns))],
                )
            ],
            keywords=[],
        ),
    )

def _ns_var ( py_ns_var : str = _NS_VAR , lisp_ns_var : str = LISP_NS_VAR , lisp_ns_ns : str = CORE_NS ) -> ast . Assign : return ast . Assign ( targets = [ ast . Name ( id = py_ns_var , ctx = ast . Store ( ) ) ] , value = ast . Call ( func = _FIND_VAR_FN_NAME , args = [ ast . Call ( func = _NEW_SYM_FN_NAME , args = [ ast . Str ( lisp_ns_var ) ] , keywords = [ ast . keyword ( arg = "ns" , value = ast . Str ( lisp_ns_ns ) ) ] , ) ] , keywords = [ ] , ) , )

Assign a Python variable named ns_var to the value of the current namespace .


def set(members: Iterable[T], meta=None) -> Set[T]:  # pylint:disable=redefined-builtin
    """Creates a new set."""
    return Set(pset(members), meta=meta)

def
set
(
members
:
Iterable
[
T
]
,
meta
=
)
->
Set
[
T
]
:
# pylint:disable=redefined-builtin
return
Set
(
pset
(
members
)
,
meta
=
meta
)


def s(*members: T, meta=None) -> Set[T]:
    """Creates a new set from members."""
    return Set(pset(members), meta=meta)

def s ( * members : T , meta = None ) -> Set [ T ] : return Set ( pset ( members ) , meta = meta )


def visit_ExceptHandler(self, node: ast.ExceptHandler) -> Optional[ast.AST]:
        """Eliminate dead code from except handler bodies."""
        new_node = self.generic_visit(node)
        assert isinstance(new_node, ast.ExceptHandler)
        return ast.copy_location(
            ast.ExceptHandler(
                type=new_node.type,
                name=new_node.name,
                body=_filter_dead_code(new_node.body),
            ),
            new_node,
        )

def visit_ExceptHandler ( self , node : ast . ExceptHandler ) -> Optional [ ast . AST ] : new_node = self . generic_visit ( node ) assert isinstance ( new_node , ast . ExceptHandler ) return ast . copy_location ( ast . ExceptHandler ( type = new_node . type , name = new_node . name , body = _filter_dead_code ( new_node . body ) , ) , new_node , )

Eliminate dead code from except handler bodies .


def visit_Expr(self, node: ast.Expr) -> Optional[ast.Expr]:
        """Eliminate no-op constant expressions which are in the tree
        as standalone statements."""
        if isinstance(
            node.value,
            (
                ast.Constant,  # type: ignore
                ast.Name,
                ast.NameConstant,
                ast.Num,
                ast.Str,
            ),
        ):
            return None
        return node

def
visit_Expr
(
self
,
node
:
ast
.
Expr
)
->
Optional
[
ast
.
Expr
]
:
if
isinstance
(
node
.
value
,
(
ast
.
Constant
,
# type: ignore
ast
.
Name
,
ast
.
NameConstant
,
ast
.
Num
,
ast
.
Str
,
)
,
)
:
return
return
node

Eliminate no - op constant expressions which are in the tree as standalone statements .


def visit_FunctionDef(self, node: ast.FunctionDef) -> Optional[ast.AST]:
        """Eliminate dead code from function bodies."""
        new_node = self.generic_visit(node)
        assert isinstance(new_node, ast.FunctionDef)
        return ast.copy_location(
            ast.FunctionDef(
                name=new_node.name,
                args=new_node.args,
                body=_filter_dead_code(new_node.body),
                decorator_list=new_node.decorator_list,
                returns=new_node.returns,
            ),
            new_node,
        )

def visit_FunctionDef ( self , node : ast . FunctionDef ) -> Optional [ ast . AST ] : new_node = self . generic_visit ( node ) assert isinstance ( new_node , ast . FunctionDef ) return ast . copy_location ( ast . FunctionDef ( name = new_node . name , args = new_node . args , body = _filter_dead_code ( new_node . body ) , decorator_list = new_node . decorator_list , returns = new_node . returns , ) , new_node , )

Eliminate dead code from function bodies .


def visit_While(self, node: ast.While) -> Optional[ast.AST]:
        """Eliminate dead code from while bodies."""
        new_node = self.generic_visit(node)
        assert isinstance(new_node, ast.While)
        return ast.copy_location(
            ast.While(
                test=new_node.test,
                body=_filter_dead_code(new_node.body),
                orelse=_filter_dead_code(new_node.orelse),
            ),
            new_node,
        )

def visit_While ( self , node : ast . While ) -> Optional [ ast . AST ] : new_node = self . generic_visit ( node ) assert isinstance ( new_node , ast . While ) return ast . copy_location ( ast . While ( test = new_node . test , body = _filter_dead_code ( new_node . body ) , orelse = _filter_dead_code ( new_node . orelse ) , ) , new_node , )


def visit_Try(self, node: ast.Try) -> Optional[ast.AST]:
        """Eliminate dead code from except try bodies."""
        new_node = self.generic_visit(node)
        assert isinstance(new_node, ast.Try)
        return ast.copy_location(
            ast.Try(
                body=_filter_dead_code(new_node.body),
                handlers=new_node.handlers,
                orelse=_filter_dead_code(new_node.orelse),
                finalbody=_filter_dead_code(new_node.finalbody),
            ),
            new_node,
        )

def visit_Try ( self , node : ast . Try ) -> Optional [ ast . AST ] : new_node = self . generic_visit ( node ) assert isinstance ( new_node , ast . Try ) return ast . copy_location ( ast . Try ( body = _filter_dead_code ( new_node . body ) , handlers = new_node . handlers , orelse = _filter_dead_code ( new_node . orelse ) , finalbody = _filter_dead_code ( new_node . finalbody ) , ) , new_node , )

Eliminate dead code from except try bodies .


def _new_module(name: str, doc=None) -> types.ModuleType:
    """Create a new empty Basilisp Python module.
    Modules are created for each Namespace when it is created."""
    mod = types.ModuleType(name, doc=doc)
    mod.__loader__ = None
    mod.__package__ = None
    mod.__spec__ = None
    mod.__basilisp_bootstrapped__ = False  # type: ignore
    return mod

def
_new_module
(
name
:
str
,
doc
=
)
->
types
.
ModuleType
:
mod
=
types
.
ModuleType
(
name
,
doc
=
doc
)
mod
.
__loader__
=
mod
.
__package__
=
mod
.
__spec__
=
mod
.
__basilisp_bootstrapped__
=
False
# type: ignore
return
mod

Create a new empty Basilisp Python module . Modules are created for each Namespace when it is created .


def first(o):
    """If o is a ISeq, return the first element from o. If o is None, return
    None. Otherwise, coerces o to a Seq and returns the first."""
    if o is None:
        return None
    if isinstance(o, ISeq):
        return o.first
    s = to_seq(o)
    if s is None:
        return None
    return s.first

def first ( o ) : if o is None : return None if isinstance ( o , ISeq ) : return o . first s = to_seq ( o ) if s is None : return None return s . first

If o is a ISeq return the first element from o . If o is None return None . Otherwise coerces o to a Seq and returns the first .


def rest(o) -> Optional[ISeq]:
    """If o is a ISeq, return the elements after the first in o. If o is None,
    returns an empty seq. Otherwise, coerces o to a seq and returns the rest."""
    if o is None:
        return None
    if isinstance(o, ISeq):
        s = o.rest
        if s is None:
            return lseq.EMPTY
        return s
    n = to_seq(o)
    if n is None:
        return lseq.EMPTY
    return n.rest

def rest ( o ) -> Optional [ ISeq ] : if o is None : return None if isinstance ( o , ISeq ) : s = o . rest if s is None : return lseq . EMPTY return s n = to_seq ( o ) if n is None : return lseq . EMPTY return n . rest

If o is a ISeq return the elements after the first in o . If o is None returns an empty seq . Otherwise coerces o to a seq and returns the rest .


def nthrest(coll, i: int):
    """Returns the nth rest sequence of coll, or coll if i is 0."""
    while True:
        if coll is None:
            return None
        if i == 0:
            return coll
        i -= 1
        coll = rest(coll)

def nthrest ( coll , i : int ) : while True : if coll is None : return None if i == 0 : return coll i -= 1 coll = rest ( coll )

Returns the nth rest sequence of coll or coll if i is 0 .


def nthnext(coll, i: int) -> Optional[ISeq]:
    """Returns the nth next sequence of coll."""
    while True:
        if coll is None:
            return None
        if i == 0:
            return to_seq(coll)
        i -= 1
        coll = next_(coll)

def nthnext ( coll , i : int ) -> Optional [ ISeq ] : while True : if coll is None : return None if i == 0 : return to_seq ( coll ) i -= 1 coll = next_ ( coll )