Skip to content

Commit

Permalink
Update API docs for glossary
Browse files Browse the repository at this point in the history
(Includes some renames and type annotations.)
  • Loading branch information
gilch committed Sep 20, 2024
1 parent d53a6c8 commit e16f6f5
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 182 deletions.
6 changes: 3 additions & 3 deletions docs/command_line_reference.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. Copyright 2020, 2021, 2022, 2023 Matthew Egan Odendahl
.. Copyright 2020, 2021, 2022, 2023, 2024 Matthew Egan Odendahl
SPDX-License-Identifier: CC-BY-SA-4.0
Command Line Reference
Expand Down Expand Up @@ -45,7 +45,7 @@ For example, using `hissp.reader.transpile`, a package name, and module names,

.. code-block:: console
$ alias lisspt="lissp -c '(hissp..transpile : :* ([#1:] sys..argv))'"
$ alias lisspt="lissp -c '(hissp..transpile : :* [##1:] sys..argv)'"
$ lisspt pkg foo # Transpiles pkg/foo.lissp to pkg/foo.py in a package context.
$ lisspt pkg.sub foo # Transpiles pkg/sub/foo.lissp to .py in subpackage context.
$ lisspt "" foo bar # foo.lissp, bar.lissp to foo.py, bar.py without a package.
Expand All @@ -54,7 +54,7 @@ or using `hissp.reader.transpile_file`, a file name, and a package name,

.. code-block:: console
$ alias lissptf="lissp -c '(hissp.reader..transpile_file : :* ([#1:] sys..argv))'"
$ alias lissptf="lissp -c '(hissp.reader..transpile_file : :* [##1:] sys..argv)'"
$ lissptf spam.lissp # Transpile a single file without a package.
$ cd pkg
$ lissptf eggs.lissp pkg # must declare the package name
Expand Down
126 changes: 80 additions & 46 deletions docs/glossary.rst

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions src/hissp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
* `hissp.repl.interact`,
as well as the `hissp.macros._macro_` namespace, making all the bundled
macros available with the shorter ``hissp.._macro_`` qualifier.
`macros` available with the shorter ``hissp.._macro_`` `qualifier`.
"""
from hissp.compiler import Compiler, readerless
from hissp.munger import demunge, munge
Expand All @@ -54,7 +54,7 @@


def prelude(env):
"""Lissp prelude shorthand tag.
"""Lissp prelude shorthand `tag`.
Usage: ``hissp..prelude#env``, which expands to
Expand All @@ -70,7 +70,7 @@ def prelude(env):


def alias(abbreviation, qualifier="hissp.macros.._macro_"):
"""Lissp alias shorthand tag.
"""Lissp alias shorthand `tag`.
Usage: ``hissp..alias## abbreviation qualifier``,
which expands to
Expand All @@ -80,15 +80,15 @@ def alias(abbreviation, qualifier="hissp.macros.._macro_"):
(hissp.macros.._macro_.alias abbreviation qualifier)
The single-argument form
``hissp..alias#abbreviation`` aliases the bundled macro qualifier.
``hissp..alias#abbreviation`` aliases the bundled macro `qualifier`.
See `hissp.macros._macro_.alias`.
"""
return "hissp.macros.._macro_.alias", abbreviation, qualifier


def refresh(module):
"""REPL convenience tag to recompile and reload a module.
"""`REPL` convenience `tag` to recompile and reload a module.
Usage: ``hissp..refresh#foo.`` where ``foo.`` evaluates to a module.
Expand Down Expand Up @@ -118,14 +118,14 @@ def refresh(module):


def subrepl(module):
"""Convenience tag to start a Lissp subREPL in the given module.
"""Convenience `tag` to start a Lissp subREPL in the given module.
Usage: ``hissp..subrepl#foo.`` where ``foo.`` evaluates to a module.
Won't re-enter current module. Prints `__name__` on subREPL exit.
(Exit a subREPL using EOF.)
See also: `refresh`, `hissp.repl.interact`.
See also: `refresh`, `hissp.repl.interact`, :term:`repl`.
"""
return (

Check warning on line 130 in src/hissp/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/hissp/__init__.py#L130

Added line #L130 was not covered by tests
(('lambda',(':','module',module,)
Expand Down
86 changes: 50 additions & 36 deletions src/hissp/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@
from pprint import pformat
from traceback import format_exc
from types import MappingProxyType, ModuleType
from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple, TypeVar, Union
from typing import (
Any,
Dict,
Iterable,
List,
NewType,
Optional,
Tuple,
TypeVar,
Union,
)
from warnings import warn

PAIR_WORDS = {":*": "*", ":**": "**", ":?": ""}
Expand All @@ -38,16 +48,21 @@
instead of its defining environment.
Rather than pass in an implicit argument to all macros,
it's available here.
`readerless` uses this automatically.
`readerless` and `macroexpand` use this automatically.
"""

MAX_PROTOCOL = pickle.HIGHEST_PROTOCOL
"""
Compiler pickle protocol limit.
When there is no known literal syntax for an `atom`,
the compiler emits a `pickle.loads` expression as a fallback.
This is the highest pickle protocol it's allowed to use.
The compiler may use Protocol 0 instead when it has a shorter `repr`,
due to the inefficient escapes required for non-printing bytes.
A lower number may be necessary if the compiled output is expected to
run on an earlier Python version than the compiler.
"""


Expand Down Expand Up @@ -187,7 +202,7 @@ def tuple(self, form: Tuple) -> str:

@_trace
def special(self, form: Tuple) -> str:
"""Try to compile as special form, else :meth:`invocation`.
"""Try to compile as a `special form`, else :meth:`invocation`.
The two special forms are ``quote`` and `lambda <lambda_>`.
Expand Down Expand Up @@ -245,8 +260,9 @@ def lambda_(self, form: Tuple) -> str:
The special `control word`\ s ``:*`` and ``:**`` designate the
remainder of the positional and keyword parameters, respectively.
Note this body evaluates expressions in sequence, for side
effects.
effects:
>>> print(readerless(
... ('lambda', (':',':*','args',':**','kwargs',)
Expand Down Expand Up @@ -356,9 +372,9 @@ def expand_macro(self, form: Tuple) -> Union[str, Sentinel]:

@classmethod
def get_macro(cls, symbol, env: Dict[str, Any]):
"""Returns the macro function for a symbol given the namespace.
"""Returns the macro function for ``symbol`` given the ``env``.
Returns ``None`` if symbol isn't a macro.
Returns ``None`` if ``symbol`` isn't a macro identifier.
"""
if type(symbol) is not str or symbol.startswith(":"):
return None
Expand Down Expand Up @@ -396,10 +412,14 @@ def call(self, form: Iterable) -> str:
Compile call form.
Any tuple that is not quoted, ``()``, or a `special` form or
`macro` is a run-time call.
`macro` is a run-time call form.
It has three parts:
(<callable> <singles> : <pairs>).
Each argument pairs with a keyword or `control word` target.
The ``:?`` target passes positionally (implied for singles).
Like Python, it has three parts:
(<callable> <args> : <kwargs>).
For example:
>>> print(readerless(
Expand All @@ -413,7 +433,7 @@ def call(self, form: Iterable) -> str:
sep=':',
end='\n\n')
Either <args> or <kwargs> may be empty:
Either <singles> or <pairs> may be empty:
>>> readerless(('foo',':',),)
'foo()'
Expand All @@ -424,18 +444,16 @@ def call(self, form: Iterable) -> str:
foo(
bar=baz)
The ``:`` is optional if the <kwargs> part is empty:
The ``:`` is optional if the <pairs> part is empty:
>>> readerless(('foo',),)
'foo()'
>>> print(readerless(('foo','bar',),),)
foo(
bar)
The <kwargs> part has implicit pairs; there must be an even number.
Use the control words ``:*`` and ``:**`` for iterable and
mapping unpacking:
Use the ``:*`` and ``:**`` targets for position and
keyword unpacking, respectively:
>>> print(readerless(
... ('print',':',':*',[1,2], 'a',3, ':*',[4], ':**',{'sep':':','end':'\n\n'},),
Expand All @@ -446,11 +464,10 @@ def call(self, form: Iterable) -> str:
*[4],
**{'sep': ':', 'end': '\n\n'})
Unlike other control words, these can be repeated,
but (as in Python) a ``*`` is not allowed to follow ``**``.
Method calls are similar to function calls:
(.<method name> <self> <args> : <kwargs>).
A method on the first (self) argument is assumed if the function
name starts with a dot:
Expand Down Expand Up @@ -528,7 +545,7 @@ def atomic(self, form) -> str:
R"""
Compile forms that evaluate to themselves.
Emits a literal if possible, otherwise falls back to `pickle`:
Returns a literal if possible, otherwise falls back to `pickle`:
>>> readerless(-4.2j)
'((-0-4.2j))'
Expand Down Expand Up @@ -595,31 +612,32 @@ def pickle(self, form) -> str:
return f"# {r}\n__import__({pickle.__name__!r}).loads({code})"

@staticmethod
def linenos(form):
lines = form.split("\n")
def linenos(code: str):
"""Adds line numbers to code for error messages."""
lines = code.split("\n")
digits = len(str(len(lines)))
return "\n".join(f"{i:0{digits}} {line}" for i, line in enumerate(lines, 1))

def eval(self, form: str, form_number: int) -> Tuple[str, ...]:
"""Execute compiled form, but only if evaluate mode is enabled."""
def eval(self, code: str, form_number: int) -> Tuple[str, ...]:
"""Execute compiled code, but only if evaluate mode is enabled."""
try:
if self.evaluate:
filename = (
f"<Compiled Hissp #{form_number} of {self.qualname}:\n"
f"{self.linenos(form)}\n"
f"{self.linenos(code)}\n"
f">"
)
exec(compile(form, filename, "exec"), self.env)
exec(compile(code, filename, "exec"), self.env)
except Exception as e:
exc = format_exc()
if self.env.get("__name__") == "__main__":
self.abort = exc
else:
warn(
f"\n {e} when evaluating form:\n{form}\n\n{exc}", PostCompileWarning
f"\n {e} when evaluating form:\n{code}\n\n{exc}", PostCompileWarning
)
return form, "# " + exc.replace("\n", "\n# ")
return (form,)
return code, "# " + exc.replace("\n", "\n# ")
return (code,)


def _join_args(*args):
Expand Down Expand Up @@ -681,16 +699,12 @@ def macroexpand(form, env: Optional[Dict[str, Any]] = None):
form = expanded


def _identity(x):
return x


def macroexpand_all(
form,
env: Optional[Dict[str, Any]] = None,
*,
preprocess=_identity,
postprocess=_identity,
preprocess=lambda x: x,
postprocess=lambda x: x,
):
"""Recursively macroexpand everything possible from the outside-in.
Expand All @@ -715,7 +729,7 @@ def macroexpand_all(
return "lambda", _pexpand(exp[1], env), *(macroexpand_all(e, env) for e in exp[2:])


def _pexpand(params, env: Optional[Dict[str, Any]]):
def _pexpand(params, env: Optional[Dict[str, Any]]) -> tuple:
if ":" not in params:
return params
singles, pairs = parse_params(params)
Expand All @@ -726,7 +740,7 @@ def _pexpand(params, env: Optional[Dict[str, Any]]):
return *singles, ":", *chain.from_iterable(pairs.items())


def parse_params(params):
def parse_params(params) -> Tuple[tuple, Dict[str, Any]]:
"""Parses a lambda form's `params` into a tuple of singles and a dict of pairs."""
iparams = iter(params)
singles = tuple(takewhile(lambda x: x != ":", iparams))
Expand Down
Loading

0 comments on commit e16f6f5

Please sign in to comment.