Skip to content

Commit 30f8314

Browse files
authored
Add a basic CLI for Basilisp REPL (#167)
* Add a basic CLI for Basilisp REPL * Fix linting issues * Okay but actually this time
1 parent fb9d465 commit 30f8314

File tree

7 files changed

+130
-83
lines changed

7 files changed

+130
-83
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ lint:
1313
.PHONY: repl
1414
repl:
1515
@pipenv install
16-
@pipenv run python -m basilisp.main
16+
@pipenv run python -m basilisp.cli repl
1717

1818

1919
.PHONY: coverage

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ yapf = "*"
1515
[packages]
1616
astor = "*"
1717
atomos = "*"
18+
click = "*"
1819
pyfunctional = "*"
1920
pyrsistent = "*"
2021
python-dateutil = "*"

Pipfile.lock

Lines changed: 36 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pip install basilisp
2020
Once Basilisp is installed, you can enter into the REPL using:
2121

2222
```bash
23-
python -m basilisp.main
23+
basilisp repl
2424
```
2525

2626
Basilisp features many of the same functions and idioms as [Clojure](https://clojure.org/)

basilisp/cli.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# noinspection PyUnresolvedReferences
2+
import readline # noqa: F401
3+
import traceback
4+
import types
5+
6+
import click
7+
8+
import basilisp.compiler as compiler
9+
import basilisp.lang.runtime as runtime
10+
import basilisp.main as basilisp
11+
import basilisp.reader as reader
12+
13+
14+
@click.group()
15+
def cli():
16+
"""Basilisp is a Lisp dialect inspired by Clojure targeting Python 3."""
17+
pass
18+
19+
20+
def eval_file(filename: str, ctx: compiler.CompilerContext, module: types.ModuleType):
21+
"""Evaluate a file with the given name into a Python module AST node."""
22+
last = None
23+
for form in reader.read_file(filename, resolver=runtime.resolve_alias):
24+
last = compiler.compile_and_exec_form(form, ctx, module, filename)
25+
return last
26+
27+
28+
def eval_str(s: str, ctx: compiler.CompilerContext, module: types.ModuleType):
29+
"""Evaluate the forms in a string into a Python module AST node."""
30+
last = None
31+
for form in reader.read_str(s, resolver=runtime.resolve_alias):
32+
last = compiler.compile_and_exec_form(form, ctx, module, source_filename='REPL Input')
33+
return last
34+
35+
36+
@cli.command(short_help='start the Basilisp REPL')
37+
@click.option('--default-ns', default=runtime._REPL_DEFAULT_NS, help='default namespace to use for the REPL')
38+
def repl(default_ns):
39+
basilisp.init()
40+
ctx = compiler.CompilerContext()
41+
ns_var = runtime.set_current_ns(default_ns)
42+
while True:
43+
ns: runtime.Namespace = ns_var.value
44+
try:
45+
lsrc = input(f'{ns.name}=> ')
46+
except EOFError:
47+
break
48+
except KeyboardInterrupt:
49+
print('')
50+
continue
51+
52+
if len(lsrc) == 0:
53+
continue
54+
55+
try:
56+
print(compiler.lrepr(eval_str(lsrc, ctx, ns.module)))
57+
except reader.SyntaxError as e:
58+
traceback.print_exception(reader.SyntaxError, e, e.__traceback__)
59+
continue
60+
except compiler.CompilerException as e:
61+
traceback.print_exception(compiler.CompilerException, e,
62+
e.__traceback__)
63+
continue
64+
except Exception as e:
65+
traceback.print_exception(Exception, e, e.__traceback__)
66+
continue
67+
68+
69+
@cli.command(short_help='run a Basilisp script or code')
70+
@click.argument('file-or-code')
71+
@click.option('-c', '--code', is_flag=True, help='if provided, treat argument as a string of code')
72+
@click.option('--in-ns', default=runtime._REPL_DEFAULT_NS, help='namespace to use for the code')
73+
def run(file_or_code, code, in_ns):
74+
"""Run a Basilisp script or a line of code, if it is provided."""
75+
basilisp.init()
76+
ctx = compiler.CompilerContext()
77+
ns_var = runtime.set_current_ns(in_ns)
78+
ns: runtime.Namespace = ns_var.value
79+
80+
if code:
81+
print(compiler.lrepr(eval_str(file_or_code, ctx, ns.module)))
82+
else:
83+
print(compiler.lrepr(eval_file(file_or_code, ctx, ns.module)))
84+
85+
86+
if __name__ == "__main__":
87+
cli()

basilisp/main.py

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,7 @@
11
import importlib
2-
# noinspection PyUnresolvedReferences
3-
import readline # noqa: F401
4-
import traceback
5-
import types
62

7-
import basilisp.compiler as compiler
83
import basilisp.importer as importer
94
import basilisp.lang.runtime as runtime
10-
import basilisp.reader as reader
11-
12-
13-
def eval_file(filename: str, ctx: compiler.CompilerContext, module: types.ModuleType):
14-
"""Evaluate a file with the given name into a Python module AST node."""
15-
last = None
16-
for form in reader.read_file(filename, resolver=runtime.resolve_alias):
17-
last = compiler.compile_and_exec_form(form, ctx, module, filename)
18-
return last
19-
20-
21-
def eval_str(s: str, ctx: compiler.CompilerContext, module: types.ModuleType):
22-
"""Evaluate the forms in a string into a Python module AST node."""
23-
last = None
24-
for form in reader.read_str(s, resolver=runtime.resolve_alias):
25-
last = compiler.compile_and_exec_form(form, ctx, module, source_filename='REPL Input')
26-
return last
27-
28-
29-
def repl(default_ns=runtime._REPL_DEFAULT_NS):
30-
ctx = compiler.CompilerContext()
31-
ns_var = runtime.set_current_ns(default_ns)
32-
while True:
33-
ns: runtime.Namespace = ns_var.value
34-
try:
35-
lsrc = input(f'{ns.name}=> ')
36-
except EOFError:
37-
break
38-
except KeyboardInterrupt:
39-
print('')
40-
continue
41-
42-
if len(lsrc) == 0:
43-
continue
44-
45-
try:
46-
print(compiler.lrepr(eval_str(lsrc, ctx, ns.module)))
47-
except reader.SyntaxError as e:
48-
traceback.print_exception(reader.SyntaxError, e, e.__traceback__)
49-
continue
50-
except compiler.CompilerException as e:
51-
traceback.print_exception(compiler.CompilerException, e,
52-
e.__traceback__)
53-
continue
54-
except Exception as e:
55-
traceback.print_exception(Exception, e, e.__traceback__)
56-
continue
575

586

597
def init():
@@ -62,8 +10,3 @@ def init():
6210
runtime.bootstrap()
6311
importer.hook_imports()
6412
importlib.import_module('basilisp.core')
65-
66-
67-
if __name__ == "__main__":
68-
init()
69-
repl()

setup.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
REQUIRED = [
2424
'astor',
2525
'atomos',
26+
'click',
2627
'pyfunctional',
2728
'pyrsistent',
2829
'pytest',
@@ -101,9 +102,9 @@ def run(self):
101102
url=URL,
102103
packages=find_packages(exclude=('tests',)),
103104

104-
# entry_points={
105-
# 'console_scripts': ['mycli=mymodule:cli'],
106-
# },
105+
entry_points={
106+
'console_scripts': ['basilisp=basilisp.cli:cli'],
107+
},
107108
install_requires=REQUIRED,
108109
extras_require=EXTRAS,
109110
include_package_data=True,

0 commit comments

Comments
 (0)