Skip to content

Commit 9477574

Browse files
committed
Add codeop.compile_command
This function in standard Python is a building block for custom REPLs: ```python from codeop import compile_command print("Repl in (Circuit-)Python") ns = {} PS1="<<< " PS2=",,, " command = "" while True: line = input(PS2 if command else PS1) if command: command = command + "\n" + line else: command = line try: if (code := compile_command(command)): command = "" exec(code, ns) except Exception as e: command = "" print(e) ```
1 parent f5a03a8 commit 9477574

File tree

6 files changed

+124
-8
lines changed

6 files changed

+124
-8
lines changed

ports/unix/variants/coverage/mpconfigvariant.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ SRC_BITMAP := \
3535
shared-bindings/audiomixer/Mixer.c \
3636
shared-bindings/audiomixer/MixerVoice.c \
3737
shared-bindings/bitmaptools/__init__.c \
38+
shared-bindings/codeop/__init__.c \
3839
shared-bindings/displayio/Bitmap.c \
3940
shared-bindings/jpegio/__init__.c \
4041
shared-bindings/jpegio/JpegDecoder.c \
@@ -87,6 +88,7 @@ CFLAGS += \
8788
-DCIRCUITPY_AUDIOMIXER=1 \
8889
-DCIRCUITPY_AUDIOCORE_DEBUG=1 \
8990
-DCIRCUITPY_BITMAPTOOLS=1 \
91+
-DCIRCUITPY_CODEOP=1 \
9092
-DCIRCUITPY_DISPLAYIO_UNIX=1 \
9193
-DCIRCUITPY_FUTURE=1 \
9294
-DCIRCUITPY_GIFIO=1 \

py/circuitpy_defns.mk

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ endif
168168
ifeq ($(CIRCUITPY_CANIO),1)
169169
SRC_PATTERNS += canio/%
170170
endif
171+
ifeq ($(CIRCUITPY_CODEOP),1)
172+
SRC_PATTERNS += codeop/%
173+
endif
171174
ifeq ($(CIRCUITPY_COUNTIO),1)
172175
SRC_PATTERNS += countio/%
173176
endif
@@ -547,6 +550,7 @@ $(filter $(SRC_PATTERNS), \
547550
__future__/__init__.c \
548551
camera/ImageFormat.c \
549552
canio/Match.c \
553+
codeop/__init__.c \
550554
countio/Edge.c \
551555
digitalio/Direction.c \
552556
digitalio/DriveMode.c \

py/circuitpy_mpconfig.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ CFLAGS += -DCIRCUITPY_CAMERA=$(CIRCUITPY_CAMERA)
182182
CIRCUITPY_CANIO ?= 0
183183
CFLAGS += -DCIRCUITPY_CANIO=$(CIRCUITPY_CANIO)
184184

185+
CIRCUITPY_CODEOP ?= $(CIRCUITPY_FULL_BUILD)
186+
CFLAGS += -DCIRCUITPY_CODEOP=$(CIRCUITPY_CODEOP)
187+
185188
CIRCUITPY_COLLECTIONS ?= 1
186189
CFLAGS += -DCIRCUITPY_COLLECTIONS=$(CIRCUITPY_COLLECTIONS)
187190

shared-bindings/codeop/__init__.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2023 Jeff Epler for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
#include <string.h>
27+
28+
#include "py/builtin.h"
29+
#include "py/obj.h"
30+
#include "py/runtime.h"
31+
#include "py/repl.h"
32+
33+
STATIC const char *get_arg_str(mp_obj_t arg, qstr name) {
34+
return mp_obj_str_get_str(mp_arg_validate_type_string(arg, name));
35+
}
36+
37+
//| """Utilities to compile possibly incomplete Python source code."""
38+
39+
//| def compile_command(source: str, filename: str = "<input>", symbol: str = "single"):
40+
//| """Compile a command and determine whether it is incomplete
41+
//|
42+
//| The 'completeness' determination is slightly different than in standard Python
43+
//| (it's whatever the internal function ``mp_repl_continue_with_input`` does).
44+
//| In particular, it's important that the code not end with a newline character
45+
//| or it is likely to be treated as a complete command."""
46+
//|
47+
STATIC mp_obj_t compile_command(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
48+
enum { ARG_source, ARG_filename, ARG_symbol };
49+
static const mp_arg_t allowed_args[] = {
50+
{ MP_QSTR_source, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = mp_const_none } },
51+
{ MP_QSTR_filename, MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
52+
{ MP_QSTR_symbol, MP_ARG_OBJ, { .u_obj = MP_ROM_QSTR(MP_QSTR_single) } },
53+
};
54+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
55+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
56+
57+
if (args[ARG_filename].u_obj == MP_OBJ_NULL) {
58+
args[ARG_filename].u_obj = mp_obj_new_str_via_qstr("<input>", 7);
59+
}
60+
const char *source = get_arg_str(args[ARG_source].u_obj, MP_QSTR_source);
61+
if (mp_repl_continue_with_input(source)) {
62+
return mp_const_none;
63+
}
64+
65+
return mp_call_function_n_kw((mp_obj_t)&mp_builtin_compile_obj, 3, 0, &args[0].u_obj);
66+
}
67+
MP_DEFINE_CONST_FUN_OBJ_KW(compile_command_obj, 1, compile_command);
68+
69+
STATIC const mp_rom_map_elem_t codeop_module_globals_table[] = {
70+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_codeop) },
71+
{ MP_ROM_QSTR(MP_QSTR_compile_command), MP_ROM_PTR(&compile_command_obj) },
72+
};
73+
74+
STATIC MP_DEFINE_CONST_DICT(codeop_module_globals, codeop_module_globals_table);
75+
76+
const mp_obj_module_t codeop_module = {
77+
.base = { &mp_type_module },
78+
.globals = (mp_obj_dict_t *)&codeop_module_globals,
79+
};
80+
81+
MP_REGISTER_MODULE(MP_QSTR_codeop, codeop_module);

tests/circuitpython/codeop_compile.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
try:
2+
from codeop import compile_command
3+
except ImportError:
4+
print("SKIP")
5+
raise SystemExit
6+
7+
8+
def test(snippet):
9+
result = compile_command(snippet)
10+
print("None" if result is None else "<code>")
11+
12+
13+
# Complete command
14+
test("3+3")
15+
16+
# Complete command
17+
test("if 1:\n print('hi mom')\n")
18+
19+
# Incomplete command
20+
test("if 1:")
21+
22+
# Incomplete multiline string
23+
test("'''I'm sure it's OK")
24+
25+
# Incomplete parenthesized expression
26+
test("[1, 2")

tests/unix/extra_coverage.py.exp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,14 @@ port
5353
builtins micropython __future__ _asyncio
5454
_thread aesio array audiocore
5555
audiomixer binascii bitmaptools cexample
56-
cmath collections cppexample displayio
57-
errno example_package gc
58-
hashlib heapq io jpegio
59-
json locale math os
60-
platform qrio rainbowio random
61-
re select struct synthio
62-
sys time traceback uctypes
63-
ulab zlib
56+
cmath codeop collections cppexample
57+
displayio errno example_package
58+
gc hashlib heapq io
59+
jpegio json locale math
60+
os platform qrio rainbowio
61+
random re select struct
62+
synthio sys time traceback
63+
uctypes ulab zlib
6464
me
6565

6666
rainbowio random

0 commit comments

Comments
 (0)