freedos-micro-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- freedos_micro_python/__init__.py +8 -0
- freedos_micro_python/cli.py +106 -0
- freedos_micro_python/gen_qstrdefs.py +275 -0
- freedos_micro_python/port/arch/bpstruct.h +2 -0
- freedos_micro_python/port/arch/cc.h +4 -0
- freedos_micro_python/port/arch/epstruct.h +1 -0
- freedos_micro_python/port/base64_uc386dos.c +164 -0
- freedos_micro_python/port/file_uc386dos.c +228 -0
- freedos_micro_python/port/lib/axtls/crypto/crypto.h +45 -0
- freedos_micro_python/port/lwip-arch-cc.h +46 -0
- freedos_micro_python/port/lwip_uc386dos.c +248 -0
- freedos_micro_python/port/lwipopts.h +117 -0
- freedos_micro_python/port/math_gamma.c +63 -0
- freedos_micro_python/port/modtime_uc386dos.c +60 -0
- freedos_micro_python/port/modtls_axtls_uc386dos.c +461 -0
- freedos_micro_python/port/mpconfigport.h +358 -0
- freedos_micro_python/port/mphal_uc386dos.c +103 -0
- freedos_micro_python/port/mphalport.h +11 -0
- freedos_micro_python/port/os_uc386dos.c +264 -0
- freedos_micro_python/port/path_uc386dos.c +307 -0
- freedos_micro_python/port/pktdrv_uc386dos.c +650 -0
- freedos_micro_python/port/qstrdefsport.h +2 -0
- freedos_micro_python/port/shutil_uc386dos.c +111 -0
- freedos_micro_python/port/tempfile_uc386dos.c +129 -0
- freedos_micro_python/port/time_real_uc386dos.c +77 -0
- freedos_micro_python/port/uc386_net_uc386dos.c +126 -0
- freedos_micro_python/port/urllib_parse_uc386dos.c +360 -0
- freedos_micro_python/port/urllib_uc386dos.c +29 -0
- freedos_micro_python/scripts/build.sh +641 -0
- freedos_micro_python/scripts/build_port.sh +241 -0
- freedos_micro_python/scripts/fetch.sh +238 -0
- freedos_micro_python-0.1.0.dist-info/METADATA +131 -0
- freedos_micro_python-0.1.0.dist-info/RECORD +37 -0
- freedos_micro_python-0.1.0.dist-info/WHEEL +5 -0
- freedos_micro_python-0.1.0.dist-info/entry_points.txt +2 -0
- freedos_micro_python-0.1.0.dist-info/licenses/LICENSE +25 -0
- freedos_micro_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""freedos-micropython CLI: builds the FreeDOS MicroPython binary.
|
|
2
|
+
|
|
3
|
+
Subcommands:
|
|
4
|
+
fetch Run scripts/fetch.sh to clone upstream MicroPython.
|
|
5
|
+
build Run scripts/build.sh (per-TU triage build, generates qstrdefs).
|
|
6
|
+
port Run scripts/build_port.sh (multi-TU build → micropython.bin).
|
|
7
|
+
|
|
8
|
+
Each subcommand is a thin wrapper around the bundled shell script. The
|
|
9
|
+
wrapper:
|
|
10
|
+
|
|
11
|
+
1. Locates uc386's lib/ via the installed package and exports
|
|
12
|
+
UC386_LIB_INCLUDE so the scripts don't have to compute it.
|
|
13
|
+
2. Picks a working directory: uses --workdir if given, else cwd.
|
|
14
|
+
3. Execs the script with stdin/stdout/stderr passed through.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import os
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from importlib.resources import files
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _uc386_lib_include() -> Path:
|
|
28
|
+
import uc386
|
|
29
|
+
return Path(uc386.__file__).resolve().parent / "lib" / "include"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _scripts_dir() -> Path:
|
|
33
|
+
return Path(str(files("freedos_micro_python") / "scripts"))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _port_dir() -> Path:
|
|
37
|
+
return Path(str(files("freedos_micro_python") / "port"))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _ensure_port_symlink(workdir: Path) -> None:
|
|
41
|
+
"""The shipped scripts reference port files as `uc386-dos/foo.c`.
|
|
42
|
+
Make sure `workdir/uc386-dos` points at the bundled port dir so
|
|
43
|
+
those references resolve regardless of where the user is running
|
|
44
|
+
from."""
|
|
45
|
+
link = workdir / "uc386-dos"
|
|
46
|
+
target = _port_dir()
|
|
47
|
+
if link.is_symlink():
|
|
48
|
+
if link.resolve() == target.resolve():
|
|
49
|
+
return
|
|
50
|
+
link.unlink()
|
|
51
|
+
elif link.exists():
|
|
52
|
+
# Real directory at uc386-dos/ — leave it alone, user knows
|
|
53
|
+
# what they're doing.
|
|
54
|
+
return
|
|
55
|
+
link.symlink_to(target)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _run_script(name: str, workdir: Path, extra_args: list[str]) -> int:
|
|
59
|
+
script = _scripts_dir() / name
|
|
60
|
+
if not script.exists():
|
|
61
|
+
print(f"freedos-micropython: bundled script not found: {script}",
|
|
62
|
+
file=sys.stderr)
|
|
63
|
+
return 1
|
|
64
|
+
env = os.environ.copy()
|
|
65
|
+
env["UC386_LIB_INCLUDE"] = str(_uc386_lib_include())
|
|
66
|
+
env["FREEDOS_MP_PORT_DIR"] = str(_port_dir())
|
|
67
|
+
env["FREEDOS_MP_SCRIPTS_DIR"] = str(_scripts_dir())
|
|
68
|
+
workdir.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
_ensure_port_symlink(workdir)
|
|
70
|
+
return subprocess.run(
|
|
71
|
+
[str(script), *extra_args],
|
|
72
|
+
cwd=workdir, env=env,
|
|
73
|
+
).returncode
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main(argv: list[str] | None = None) -> int:
|
|
77
|
+
ap = argparse.ArgumentParser(
|
|
78
|
+
prog="freedos-micropython",
|
|
79
|
+
description=(
|
|
80
|
+
"Build the FreeDOS / i386 MicroPython port end-to-end "
|
|
81
|
+
"through the uc386 C23 compiler. Produces a flat .bin "
|
|
82
|
+
"runnable under uc386.dos_emu (or a PMODE/W .exe via "
|
|
83
|
+
"uc386's exe.py harness)."
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
ap.add_argument(
|
|
87
|
+
"--workdir", type=Path, default=Path.cwd(),
|
|
88
|
+
help="Directory the build runs in (upstream/ and build/ "
|
|
89
|
+
"land here). Default: current dir.",
|
|
90
|
+
)
|
|
91
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
92
|
+
sub.add_parser("fetch", help="Clone upstream micropython into ./upstream")
|
|
93
|
+
sub.add_parser("build", help="Per-TU triage build (generates qstrdefs)")
|
|
94
|
+
sub.add_parser("port", help="Multi-TU build → build/micropython.bin")
|
|
95
|
+
|
|
96
|
+
ns, extra = ap.parse_known_args(argv)
|
|
97
|
+
script_map = {
|
|
98
|
+
"fetch": "fetch.sh",
|
|
99
|
+
"build": "build.sh",
|
|
100
|
+
"port": "build_port.sh",
|
|
101
|
+
}
|
|
102
|
+
return _run_script(script_map[ns.cmd], ns.workdir, extra)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Reverse-mangle MP_QSTR_<sanitized> macro names back to the
|
|
3
|
+
original qstr source string.
|
|
4
|
+
|
|
5
|
+
Upstream's `tools/makeqstrdata.py:qstr_escape` walks each qstr and
|
|
6
|
+
replaces every non-`[A-Za-z0-9]` byte with `_<name>_`, where
|
|
7
|
+
`<name>` comes from `codepoint2name` (HTML entity names + a small
|
|
8
|
+
custom map) or `0x%02x` for the rest. Our triage build greps for
|
|
9
|
+
`MP_QSTR_<sanitized>` references in the source; the *macro* name
|
|
10
|
+
is what's been sanitized — the original qstr string for `\\n` is
|
|
11
|
+
1 byte, but it appears in source as `MP_QSTR__0x0a_` (5-byte
|
|
12
|
+
sanitized form). If we emit the sanitized form as the qstr's
|
|
13
|
+
4th-field string, `qstr_find_strn("\\n", 1)` misses, AND output
|
|
14
|
+
qstrs (e.g. `print()`'s trailing newline) render as the literal
|
|
15
|
+
text `_0x0a_` instead of a newline.
|
|
16
|
+
|
|
17
|
+
This script reads MP_QSTR_<x> tokens on stdin (one per line, may
|
|
18
|
+
contain dups), reverses the escape, and emits one
|
|
19
|
+
`QDEF1(macro, 0, len, "<orig>")` line per UNIQUE token sorted by
|
|
20
|
+
the **original string** (ASCII byte order). Sort key matters:
|
|
21
|
+
qstr_find_strn does `strncmp(probe_str, pool->qstrs[mid], n)` —
|
|
22
|
+
the comparison key at runtime is the un-escaped string, so the
|
|
23
|
+
pool's `is_sorted=true` invariant requires that order. Sorting
|
|
24
|
+
by macro name happens to coincide for pure-identifier qstrs
|
|
25
|
+
(`print`, `__name__`) but breaks for escaped ones
|
|
26
|
+
(`MP_QSTR__0x0a_` lex-orders near `_`, while its actual string
|
|
27
|
+
`\\n` = 0x0A would sort before space).
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import sys
|
|
32
|
+
|
|
33
|
+
# Pull codepoint2name from upstream verbatim — same source of truth
|
|
34
|
+
# as makeqstrdata.qstr_escape so the inverse is exact. Caller is
|
|
35
|
+
# expected to set sys.path so `upstream.py.makeqstrdata` resolves.
|
|
36
|
+
sys.path.insert(0, "upstream/py")
|
|
37
|
+
from makeqstrdata import codepoint2name # type: ignore[import-not-found]
|
|
38
|
+
|
|
39
|
+
# Inverse map: HTML entity name -> single-character byte string.
|
|
40
|
+
# Two filters:
|
|
41
|
+
# 1. Codepoint must be < 256 — qstrs are byte sequences, so
|
|
42
|
+
# escapes for high-codepoint Unicode chars never appear in real
|
|
43
|
+
# source. Without this, `_omega_` etc. would false-match.
|
|
44
|
+
# 2. The decoded char must NOT itself be an identifier char
|
|
45
|
+
# (`[A-Za-z0-9_]`). Upstream's `qstr_escape` only produces
|
|
46
|
+
# `_<name>_` wrappers for NON-identifier chars (the regex
|
|
47
|
+
# `RE_NO_ESCAPE = r"[A-Za-z0-9_]"` passes identifier chars
|
|
48
|
+
# through unchanged). So `_<name>_` in a macro tail can only
|
|
49
|
+
# have come from escaping a punctuation/whitespace byte —
|
|
50
|
+
# never from an alphanumeric escape. This filter eliminates
|
|
51
|
+
# false matches like `__not__` (a real Python dunder, not an
|
|
52
|
+
# escape of `¬` U+00AC) and `__and__` (likewise, not `∧`).
|
|
53
|
+
_IDENT_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")
|
|
54
|
+
# Restrict to ASCII printable punctuation (32–126, excluding the
|
|
55
|
+
# identifier subset). Control chars (`\n`, `\t`, …) and high-byte
|
|
56
|
+
# chars (¬ U+00AC, ∧ U+2227, Α U+0391, …) are handled either via
|
|
57
|
+
# the `0x%02x` literal path or are simply unreachable in real qstr
|
|
58
|
+
# source — `__not__` is a real Python dunder, not an escape for `¬`.
|
|
59
|
+
name2char = {
|
|
60
|
+
name: chr(cp)
|
|
61
|
+
for cp, name in codepoint2name.items()
|
|
62
|
+
if 32 <= cp <= 126 and chr(cp) not in _IDENT_CHARS
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def unescape(macro_tail: str) -> str:
|
|
67
|
+
"""Reverse qstr_escape on the part after `MP_QSTR_`.
|
|
68
|
+
|
|
69
|
+
Walks left-to-right. Plain `[A-Za-z0-9]` runs pass through.
|
|
70
|
+
A `_<name>_` group decodes to a single byte: `name` is either
|
|
71
|
+
an HTML entity name (`lt`, `gt`, `space`, `hyphen`, ...) or a
|
|
72
|
+
`0x%02x` literal. The leading `_` and trailing `_` framing the
|
|
73
|
+
name come from `qstr_escape`'s `"_" + name + "_"` template.
|
|
74
|
+
|
|
75
|
+
Edge case: an underscore that's part of the *original* string
|
|
76
|
+
(e.g. `__name__`) ALSO gets sanitized — but to itself, since
|
|
77
|
+
`_` is in `RE_NO_ESCAPE` upstream:
|
|
78
|
+
|
|
79
|
+
RE_NO_ESCAPE = re.compile(r"[A-Za-z0-9_]")
|
|
80
|
+
|
|
81
|
+
So plain `_` runs through verbatim. The only `_<name>_` groups
|
|
82
|
+
that exist are the genuine escape sequences. Disambiguating
|
|
83
|
+
works because every escape `name` is at minimum 2 chars
|
|
84
|
+
(`lt`, `gt`, `0x..`) — a lone `_` is never a wrapper.
|
|
85
|
+
"""
|
|
86
|
+
out: list[str] = []
|
|
87
|
+
i = 0
|
|
88
|
+
n = len(macro_tail)
|
|
89
|
+
while i < n:
|
|
90
|
+
c = macro_tail[i]
|
|
91
|
+
if c != "_":
|
|
92
|
+
out.append(c)
|
|
93
|
+
i += 1
|
|
94
|
+
continue
|
|
95
|
+
# Found a `_` — could be a literal underscore in the source
|
|
96
|
+
# qstr (e.g. `__name__`) or the start of `_<name>_` where
|
|
97
|
+
# `<name>` is an entry in `codepoint2name` (HTML entity name)
|
|
98
|
+
# or `0xNN` (hex byte literal). Some entity names CONTAIN
|
|
99
|
+
# underscores themselves: `brace_open`, `brace_close`,
|
|
100
|
+
# `paren_open`, `bracket_open`, etc. So a naive
|
|
101
|
+
# `find("_", i+1)` would split `_brace_open_` on the inner
|
|
102
|
+
# `_` and produce `brace` as the candidate name (which
|
|
103
|
+
# doesn't match anything, so we'd silently drop the escape).
|
|
104
|
+
# Iterate forward over EVERY `_` that follows and accept the
|
|
105
|
+
# first candidate that's a known name (longest valid by
|
|
106
|
+
# construction — names don't share prefixes that are also
|
|
107
|
+
# names). Fall back to literal `_` if no closing `_` matches.
|
|
108
|
+
matched = False
|
|
109
|
+
j = i + 1
|
|
110
|
+
while True:
|
|
111
|
+
k = macro_tail.find("_", j)
|
|
112
|
+
if k == -1:
|
|
113
|
+
break
|
|
114
|
+
candidate = macro_tail[i + 1 : k]
|
|
115
|
+
if candidate in name2char:
|
|
116
|
+
out.append(name2char[candidate])
|
|
117
|
+
i = k + 1
|
|
118
|
+
matched = True
|
|
119
|
+
break
|
|
120
|
+
if (
|
|
121
|
+
len(candidate) == 4
|
|
122
|
+
and candidate[:2] == "0x"
|
|
123
|
+
and all(ch in "0123456789abcdef" for ch in candidate[2:])
|
|
124
|
+
):
|
|
125
|
+
out.append(chr(int(candidate[2:], 16)))
|
|
126
|
+
i = k + 1
|
|
127
|
+
matched = True
|
|
128
|
+
break
|
|
129
|
+
j = k + 1
|
|
130
|
+
if not matched:
|
|
131
|
+
out.append("_")
|
|
132
|
+
i += 1
|
|
133
|
+
return "".join(out)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def compute_hash(qbytes: bytes, bytes_hash: int) -> int:
|
|
137
|
+
"""djb2 hash, mirrored from upstream `tools/makeqstrdata.py:compute_hash`.
|
|
138
|
+
Required at `MICROPY_QSTR_BYTES_IN_HASH > 0` (CORE_FEATURES and
|
|
139
|
+
above): `qstr_find_strn`'s post-binary-search filter does
|
|
140
|
+
`pool->hashes[at] == str_hash` before memcmp. If the QDEF emits 0
|
|
141
|
+
for the hash but the runtime computes a real hash, every static
|
|
142
|
+
lookup misses and `print`/`__name__` NameError at runtime.
|
|
143
|
+
|
|
144
|
+
Mirrors upstream's `(hash & mask) or 1` zero-fix; mask width is
|
|
145
|
+
`8 * bytes_hash`, with `bytes_hash == 0` falling back to a 16-bit
|
|
146
|
+
mask so the value still fits in `qstr_hash_t = uint16_t` if the
|
|
147
|
+
port flips to a wider hash."""
|
|
148
|
+
h = 5381
|
|
149
|
+
for b in qbytes:
|
|
150
|
+
h = (h * 33) ^ b
|
|
151
|
+
mask = (1 << (8 * (bytes_hash or 2))) - 1
|
|
152
|
+
return (h & mask) or 1
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def c_string(s: str) -> str:
|
|
156
|
+
"""Render `s` as a C string literal — escape `"`, `\\`, and any
|
|
157
|
+
byte outside printable ASCII."""
|
|
158
|
+
parts: list[str] = []
|
|
159
|
+
for ch in s:
|
|
160
|
+
b = ord(ch)
|
|
161
|
+
if ch == "\\":
|
|
162
|
+
parts.append("\\\\")
|
|
163
|
+
elif ch == '"':
|
|
164
|
+
parts.append('\\"')
|
|
165
|
+
elif ch == "\n":
|
|
166
|
+
parts.append("\\n")
|
|
167
|
+
elif ch == "\t":
|
|
168
|
+
parts.append("\\t")
|
|
169
|
+
elif ch == "\r":
|
|
170
|
+
parts.append("\\r")
|
|
171
|
+
elif 0x20 <= b < 0x7F:
|
|
172
|
+
parts.append(ch)
|
|
173
|
+
else:
|
|
174
|
+
parts.append(f"\\x{b:02x}")
|
|
175
|
+
return '"' + "".join(parts) + '"'
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def main() -> int:
|
|
179
|
+
# Optional `--bytes-hash N` selects the qstr-hash width (matches
|
|
180
|
+
# `MICROPY_QSTR_BYTES_IN_HASH` in mpconfigport.h). Default 1 ==
|
|
181
|
+
# CORE_FEATURES; 0 == MINIMUM (hash field is unused at runtime
|
|
182
|
+
# but we still emit a non-zero value so a future ROM-level bump
|
|
183
|
+
# works without regenerating qstrdefs).
|
|
184
|
+
bytes_hash = 1
|
|
185
|
+
args = sys.argv[1:]
|
|
186
|
+
i = 0
|
|
187
|
+
while i < len(args):
|
|
188
|
+
if args[i] == "--bytes-hash" and i + 1 < len(args):
|
|
189
|
+
bytes_hash = int(args[i + 1])
|
|
190
|
+
i += 2
|
|
191
|
+
else:
|
|
192
|
+
sys.stderr.write(f"gen_qstrdefs: unknown arg {args[i]!r}\n")
|
|
193
|
+
return 2
|
|
194
|
+
|
|
195
|
+
# Pull the static + unsorted qstr lists from upstream so the
|
|
196
|
+
# qstrs the runtime needs to fit in a `byte` (e.g. the
|
|
197
|
+
# mp_binary_op_method_name table at py/objtype.c:483 — entries
|
|
198
|
+
# like `__add__` are stored as 1-byte qstr ids) end up in the
|
|
199
|
+
# static (QDEF0) pool with id < 256. Without this, enabling
|
|
200
|
+
# MICROPY_PY_ALL_SPECIAL_METHODS truncates `MP_QSTR___add__`'s
|
|
201
|
+
# >256 id to its low byte and `V(2)+V(3)` dispatches to
|
|
202
|
+
# whatever qstr happens to live at id-modulo-256 (we saw
|
|
203
|
+
# `FLOAT32` instead of `__add__`).
|
|
204
|
+
from makeqstrdata import ( # type: ignore[import-not-found]
|
|
205
|
+
static_qstr_list,
|
|
206
|
+
unsorted_qstr_list,
|
|
207
|
+
)
|
|
208
|
+
static_pool = set(static_qstr_list) | set(unsorted_qstr_list)
|
|
209
|
+
|
|
210
|
+
seen: dict[str, str] = {} # macro -> original
|
|
211
|
+
for line in sys.stdin:
|
|
212
|
+
macro = line.strip()
|
|
213
|
+
if not macro.startswith("MP_QSTR_"):
|
|
214
|
+
continue
|
|
215
|
+
if macro in seen:
|
|
216
|
+
continue
|
|
217
|
+
# `MP_QSTR_` is 8 chars; the rest is the sanitized qstr.
|
|
218
|
+
seen[macro] = unescape(macro[8:])
|
|
219
|
+
|
|
220
|
+
# Emit QDEF0 entries first (in upstream's defined order) so they
|
|
221
|
+
# get fixed id slots < 256. Then QDEF1 entries sorted by the
|
|
222
|
+
# original string (ASCII byte order) — that's the runtime's
|
|
223
|
+
# binary-search key.
|
|
224
|
+
out_w = sys.stdout.write
|
|
225
|
+
|
|
226
|
+
static_emitted: set[str] = set()
|
|
227
|
+
# Walk upstream's static_qstr_list verbatim so the .mpy ABI
|
|
228
|
+
# ID assignments stay stable across uc386 + upstream builds.
|
|
229
|
+
for original in static_qstr_list:
|
|
230
|
+
# Find the macro name for this string from `seen`. If our
|
|
231
|
+
# grep didn't capture it, synthesize the macro: upstream's
|
|
232
|
+
# qstr_escape can be re-applied via the `unescape` inverse,
|
|
233
|
+
# but we already have a forward `qstr_escape` in
|
|
234
|
+
# makeqstrdata, so use that.
|
|
235
|
+
from makeqstrdata import qstr_escape # type: ignore
|
|
236
|
+
macro = "MP_QSTR_" + qstr_escape(original)
|
|
237
|
+
qhash = compute_hash(original.encode("utf-8"), bytes_hash)
|
|
238
|
+
out_w(
|
|
239
|
+
f"QDEF0({macro}, {qhash}, {len(original)}, "
|
|
240
|
+
f"{c_string(original)})\n"
|
|
241
|
+
)
|
|
242
|
+
static_emitted.add(macro)
|
|
243
|
+
|
|
244
|
+
# Then unsorted_qstr_list — also QDEF0 (low ids), but ordering
|
|
245
|
+
# doesn't matter for .mpy compat (these aren't part of the
|
|
246
|
+
# public ABI list).
|
|
247
|
+
for original in sorted(unsorted_qstr_list):
|
|
248
|
+
from makeqstrdata import qstr_escape # type: ignore
|
|
249
|
+
macro = "MP_QSTR_" + qstr_escape(original)
|
|
250
|
+
if macro in static_emitted:
|
|
251
|
+
continue
|
|
252
|
+
qhash = compute_hash(original.encode("utf-8"), bytes_hash)
|
|
253
|
+
out_w(
|
|
254
|
+
f"QDEF0({macro}, {qhash}, {len(original)}, "
|
|
255
|
+
f"{c_string(original)})\n"
|
|
256
|
+
)
|
|
257
|
+
static_emitted.add(macro)
|
|
258
|
+
|
|
259
|
+
# Everything else goes to QDEF1, sorted for the binary-search
|
|
260
|
+
# invariant.
|
|
261
|
+
for macro, original in sorted(
|
|
262
|
+
seen.items(), key=lambda item: (item[1].encode("utf-8"), item[0])
|
|
263
|
+
):
|
|
264
|
+
if macro in static_emitted or original in static_pool:
|
|
265
|
+
continue
|
|
266
|
+
qhash = compute_hash(original.encode("utf-8"), bytes_hash)
|
|
267
|
+
out_w(
|
|
268
|
+
f"QDEF1({macro}, {qhash}, {len(original)}, "
|
|
269
|
+
f"{c_string(original)})\n"
|
|
270
|
+
)
|
|
271
|
+
return 0
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Empty pack-struct end marker.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// uc386-dos `base64` module — thin port-supplied stdlib shim.
|
|
2
|
+
//
|
|
3
|
+
// MicroPython ships base64 routines in `binascii` (b2a_base64 /
|
|
4
|
+
// a2b_base64) but most CPython programs reach for `import base64;
|
|
5
|
+
// base64.b64encode(...)` directly. Rather than freezing a Python
|
|
6
|
+
// wrapper, we ship a tiny C module with inline RFC 4648 base64 +
|
|
7
|
+
// base16 encoders/decoders. ~80 lines, no allocations beyond the
|
|
8
|
+
// vstr that holds the result.
|
|
9
|
+
//
|
|
10
|
+
// Surface:
|
|
11
|
+
// base64.b64encode(data) → bytes (no trailing newline)
|
|
12
|
+
// base64.b64decode(s) → bytes
|
|
13
|
+
// base64.b16encode(data) → uppercase hex bytes
|
|
14
|
+
// base64.b16decode(s) → bytes (case-insensitive accepted)
|
|
15
|
+
|
|
16
|
+
#include <string.h>
|
|
17
|
+
|
|
18
|
+
#include "py/runtime.h"
|
|
19
|
+
#include "py/objstr.h"
|
|
20
|
+
#include "py/binary.h"
|
|
21
|
+
|
|
22
|
+
static const char b64_alphabet[] =
|
|
23
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
24
|
+
|
|
25
|
+
static int b64_decode_char(int c) {
|
|
26
|
+
if (c >= 'A' && c <= 'Z') return c - 'A';
|
|
27
|
+
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
|
28
|
+
if (c >= '0' && c <= '9') return c - '0' + 52;
|
|
29
|
+
if (c == '+') return 62;
|
|
30
|
+
if (c == '/') return 63;
|
|
31
|
+
return -1; // invalid (also catches '=' padding)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static mp_obj_t base64_b64encode(mp_obj_t data_in) {
|
|
35
|
+
mp_buffer_info_t buf;
|
|
36
|
+
mp_get_buffer_raise(data_in, &buf, MP_BUFFER_READ);
|
|
37
|
+
const unsigned char *src = (const unsigned char *)buf.buf;
|
|
38
|
+
size_t n = buf.len;
|
|
39
|
+
|
|
40
|
+
vstr_t vstr;
|
|
41
|
+
vstr_init_len(&vstr, ((n + 2) / 3) * 4);
|
|
42
|
+
char *out = vstr.buf;
|
|
43
|
+
|
|
44
|
+
size_t i = 0;
|
|
45
|
+
for (; i + 3 <= n; i += 3) {
|
|
46
|
+
unsigned int v = ((unsigned int)src[i] << 16) |
|
|
47
|
+
((unsigned int)src[i + 1] << 8) |
|
|
48
|
+
(unsigned int)src[i + 2];
|
|
49
|
+
*out++ = b64_alphabet[(v >> 18) & 0x3F];
|
|
50
|
+
*out++ = b64_alphabet[(v >> 12) & 0x3F];
|
|
51
|
+
*out++ = b64_alphabet[(v >> 6) & 0x3F];
|
|
52
|
+
*out++ = b64_alphabet[v & 0x3F];
|
|
53
|
+
}
|
|
54
|
+
size_t rem = n - i;
|
|
55
|
+
if (rem == 1) {
|
|
56
|
+
unsigned int v = (unsigned int)src[i] << 16;
|
|
57
|
+
*out++ = b64_alphabet[(v >> 18) & 0x3F];
|
|
58
|
+
*out++ = b64_alphabet[(v >> 12) & 0x3F];
|
|
59
|
+
*out++ = '=';
|
|
60
|
+
*out++ = '=';
|
|
61
|
+
} else if (rem == 2) {
|
|
62
|
+
unsigned int v = ((unsigned int)src[i] << 16) |
|
|
63
|
+
((unsigned int)src[i + 1] << 8);
|
|
64
|
+
*out++ = b64_alphabet[(v >> 18) & 0x3F];
|
|
65
|
+
*out++ = b64_alphabet[(v >> 12) & 0x3F];
|
|
66
|
+
*out++ = b64_alphabet[(v >> 6) & 0x3F];
|
|
67
|
+
*out++ = '=';
|
|
68
|
+
}
|
|
69
|
+
return mp_obj_new_bytes_from_vstr(&vstr);
|
|
70
|
+
}
|
|
71
|
+
static MP_DEFINE_CONST_FUN_OBJ_1(base64_b64encode_obj, base64_b64encode);
|
|
72
|
+
|
|
73
|
+
static mp_obj_t base64_b64decode(mp_obj_t s_in) {
|
|
74
|
+
mp_buffer_info_t buf;
|
|
75
|
+
mp_get_buffer_raise(s_in, &buf, MP_BUFFER_READ);
|
|
76
|
+
const unsigned char *src = (const unsigned char *)buf.buf;
|
|
77
|
+
size_t n = buf.len;
|
|
78
|
+
|
|
79
|
+
vstr_t vstr;
|
|
80
|
+
vstr_init(&vstr, (n * 3) / 4 + 4);
|
|
81
|
+
int bits = 0;
|
|
82
|
+
int collected = 0;
|
|
83
|
+
for (size_t i = 0; i < n; i++) {
|
|
84
|
+
int c = src[i];
|
|
85
|
+
if (c == '\r' || c == '\n' || c == ' ' || c == '\t') {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (c == '=') {
|
|
89
|
+
break; // padding — no more data
|
|
90
|
+
}
|
|
91
|
+
int v = b64_decode_char(c);
|
|
92
|
+
if (v < 0) {
|
|
93
|
+
mp_raise_ValueError(MP_ERROR_TEXT("invalid base64 character"));
|
|
94
|
+
}
|
|
95
|
+
bits = (bits << 6) | v;
|
|
96
|
+
collected += 6;
|
|
97
|
+
if (collected >= 8) {
|
|
98
|
+
collected -= 8;
|
|
99
|
+
vstr_add_byte(&vstr, (bits >> collected) & 0xFF);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return mp_obj_new_bytes_from_vstr(&vstr);
|
|
103
|
+
}
|
|
104
|
+
static MP_DEFINE_CONST_FUN_OBJ_1(base64_b64decode_obj, base64_b64decode);
|
|
105
|
+
|
|
106
|
+
static mp_obj_t base64_b16encode(mp_obj_t data_in) {
|
|
107
|
+
mp_buffer_info_t buf;
|
|
108
|
+
mp_get_buffer_raise(data_in, &buf, MP_BUFFER_READ);
|
|
109
|
+
const unsigned char *src = (const unsigned char *)buf.buf;
|
|
110
|
+
static const char hex[] = "0123456789ABCDEF";
|
|
111
|
+
|
|
112
|
+
vstr_t vstr;
|
|
113
|
+
vstr_init_len(&vstr, buf.len * 2);
|
|
114
|
+
char *out = vstr.buf;
|
|
115
|
+
for (size_t i = 0; i < buf.len; i++) {
|
|
116
|
+
*out++ = hex[(src[i] >> 4) & 0x0F];
|
|
117
|
+
*out++ = hex[src[i] & 0x0F];
|
|
118
|
+
}
|
|
119
|
+
return mp_obj_new_bytes_from_vstr(&vstr);
|
|
120
|
+
}
|
|
121
|
+
static MP_DEFINE_CONST_FUN_OBJ_1(base64_b16encode_obj, base64_b16encode);
|
|
122
|
+
|
|
123
|
+
static int hex_digit(int c) {
|
|
124
|
+
if (c >= '0' && c <= '9') return c - '0';
|
|
125
|
+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
126
|
+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
127
|
+
return -1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static mp_obj_t base64_b16decode(mp_obj_t s_in) {
|
|
131
|
+
mp_buffer_info_t buf;
|
|
132
|
+
mp_get_buffer_raise(s_in, &buf, MP_BUFFER_READ);
|
|
133
|
+
const unsigned char *src = (const unsigned char *)buf.buf;
|
|
134
|
+
if (buf.len & 1) {
|
|
135
|
+
mp_raise_ValueError(MP_ERROR_TEXT("odd-length base16 input"));
|
|
136
|
+
}
|
|
137
|
+
vstr_t vstr;
|
|
138
|
+
vstr_init_len(&vstr, buf.len / 2);
|
|
139
|
+
char *out = vstr.buf;
|
|
140
|
+
for (size_t i = 0; i < buf.len; i += 2) {
|
|
141
|
+
int hi = hex_digit(src[i]);
|
|
142
|
+
int lo = hex_digit(src[i + 1]);
|
|
143
|
+
if (hi < 0 || lo < 0) {
|
|
144
|
+
mp_raise_ValueError(MP_ERROR_TEXT("invalid base16 character"));
|
|
145
|
+
}
|
|
146
|
+
*out++ = (hi << 4) | lo;
|
|
147
|
+
}
|
|
148
|
+
return mp_obj_new_bytes_from_vstr(&vstr);
|
|
149
|
+
}
|
|
150
|
+
static MP_DEFINE_CONST_FUN_OBJ_1(base64_b16decode_obj, base64_b16decode);
|
|
151
|
+
|
|
152
|
+
static const mp_rom_map_elem_t mp_module_base64_globals_table[] = {
|
|
153
|
+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_base64) },
|
|
154
|
+
{ MP_ROM_QSTR(MP_QSTR_b64encode), MP_ROM_PTR(&base64_b64encode_obj) },
|
|
155
|
+
{ MP_ROM_QSTR(MP_QSTR_b64decode), MP_ROM_PTR(&base64_b64decode_obj) },
|
|
156
|
+
{ MP_ROM_QSTR(MP_QSTR_b16encode), MP_ROM_PTR(&base64_b16encode_obj) },
|
|
157
|
+
{ MP_ROM_QSTR(MP_QSTR_b16decode), MP_ROM_PTR(&base64_b16decode_obj) },
|
|
158
|
+
};
|
|
159
|
+
static MP_DEFINE_CONST_DICT(mp_module_base64_globals, mp_module_base64_globals_table);
|
|
160
|
+
|
|
161
|
+
const mp_obj_module_t mp_module_base64 = {
|
|
162
|
+
.base = { &mp_type_module },
|
|
163
|
+
.globals = (mp_obj_dict_t *)&mp_module_base64_globals,
|
|
164
|
+
};
|