esolangs 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.
- esolangs/__init__.py +188 -0
- esolangs/__main__.py +6 -0
- esolangs/cli.py +145 -0
- esolangs/compilers/__init__.py +1 -0
- esolangs/compilers/_riscv_common.py +143 -0
- esolangs/compilers/addsubjump.py +178 -0
- esolangs/compilers/bf_pda.py +84 -0
- esolangs/compilers/bfstack.py +146 -0
- esolangs/compilers/collatz_multiverse.py +330 -0
- esolangs/compilers/decleq.py +125 -0
- esolangs/compilers/forth.py +554 -0
- esolangs/compilers/home_row.py +174 -0
- esolangs/compilers/jaune.py +314 -0
- esolangs/compilers/ram0.py +229 -0
- esolangs/compilers/sbleq.py +145 -0
- esolangs/compilers/suffolk.py +153 -0
- esolangs/compilers/unsquare.py +188 -0
- esolangs/debug.py +152 -0
- esolangs/exceptions.py +32 -0
- esolangs/interpreters/__init__.py +1 -0
- esolangs/interpreters/_template.py +92 -0
- esolangs/interpreters/brackets.py +24 -0
- esolangs/interpreters/grid_based/__init__.py +1 -0
- esolangs/interpreters/grid_based/a_painter_ant.py +150 -0
- esolangs/interpreters/grid_based/arrowqueue.py +100 -0
- esolangs/interpreters/grid_based/circuit_diagram.py +801 -0
- esolangs/interpreters/grid_based/clockwise.py +136 -0
- esolangs/interpreters/grid_based/cod.py +250 -0
- esolangs/interpreters/grid_based/dig.py +172 -0
- esolangs/interpreters/grid_based/flowchart.py +594 -0
- esolangs/interpreters/grid_based/laserfuck.py +187 -0
- esolangs/interpreters/grid_based/streetcode.py +1117 -0
- esolangs/interpreters/grid_based/wii2d.py +163 -0
- esolangs/interpreters/io.py +157 -0
- esolangs/interpreters/memory.py +22 -0
- esolangs/interpreters/oisc_cli.py +47 -0
- esolangs/interpreters/other/__init__.py +1 -0
- esolangs/interpreters/other/container.py +140 -0
- esolangs/interpreters/other/forbin.py +782 -0
- esolangs/interpreters/other/lamfunc.py +522 -0
- esolangs/interpreters/other/suptiftam.py +729 -0
- esolangs/interpreters/other/ztoalc_l.py +230 -0
- esolangs/interpreters/queue_based/__init__.py +1 -0
- esolangs/interpreters/queue_based/bitdeque.py +105 -0
- esolangs/interpreters/queue_based/taglate.py +176 -0
- esolangs/interpreters/register_based/__init__.py +1 -0
- esolangs/interpreters/register_based/addsubjump.py +150 -0
- esolangs/interpreters/register_based/between.py +308 -0
- esolangs/interpreters/register_based/bio.py +153 -0
- esolangs/interpreters/register_based/collatz_multiverse.py +132 -0
- esolangs/interpreters/register_based/decleq.py +95 -0
- esolangs/interpreters/register_based/minsky_swap.py +135 -0
- esolangs/interpreters/register_based/myscript.py +449 -0
- esolangs/interpreters/register_based/nevermind.py +162 -0
- esolangs/interpreters/register_based/pct_squared_minus_one.py +87 -0
- esolangs/interpreters/register_based/point_break.py +336 -0
- esolangs/interpreters/register_based/polynomial.py +298 -0
- esolangs/interpreters/register_based/qoibl.py +121 -0
- esolangs/interpreters/register_based/ram0.py +106 -0
- esolangs/interpreters/register_based/sophie.py +194 -0
- esolangs/interpreters/stack_based/__init__.py +1 -0
- esolangs/interpreters/stack_based/bf_pda.py +138 -0
- esolangs/interpreters/stack_based/bfstack.py +108 -0
- esolangs/interpreters/stack_based/eval.py +124 -0
- esolangs/interpreters/stack_based/forth.py +242 -0
- esolangs/interpreters/stack_based/grapheme.py +362 -0
- esolangs/interpreters/stack_based/modulous.py +231 -0
- esolangs/interpreters/stack_based/three_x.py +155 -0
- esolangs/interpreters/stack_based/unsquare.py +139 -0
- esolangs/interpreters/tape_based/__init__.py +1 -0
- esolangs/interpreters/tape_based/back.py +109 -0
- esolangs/interpreters/tape_based/basicfuck.py +447 -0
- esolangs/interpreters/tape_based/bit_tilde.py +126 -0
- esolangs/interpreters/tape_based/brainfuck.py +81 -0
- esolangs/interpreters/tape_based/brainif.py +95 -0
- esolangs/interpreters/tape_based/circlefuck.py +152 -0
- esolangs/interpreters/tape_based/dimensional.py +274 -0
- esolangs/interpreters/tape_based/factor.py +84 -0
- esolangs/interpreters/tape_based/home_row.py +128 -0
- esolangs/interpreters/tape_based/jaune.py +229 -0
- esolangs/interpreters/tape_based/minifuck.py +83 -0
- esolangs/interpreters/tape_based/nocomment.py +122 -0
- esolangs/interpreters/tape_based/one_two_three.py +131 -0
- esolangs/interpreters/tape_based/painfuck.py +246 -0
- esolangs/interpreters/tape_based/rotfuck.py +204 -0
- esolangs/interpreters/tape_based/sbleq.py +134 -0
- esolangs/interpreters/tape_based/six_five.py +133 -0
- esolangs/interpreters/tape_based/slow_acv_mammalian.py +161 -0
- esolangs/interpreters/tape_based/suffolk.py +96 -0
- esolangs/interpreters/tape_based/three_d_brainfuck.py +145 -0
- esolangs/py.typed +0 -0
- esolangs/registry.py +478 -0
- esolangs/tools/__init__.py +1 -0
- esolangs/tools/_polynomial.py +42 -0
- esolangs/tools/boolean/__init__.py +207 -0
- esolangs/tools/boolean/a_painter_ant.py +263 -0
- esolangs/tools/boolean/circuit_diagram.py +420 -0
- esolangs/tools/boolean/cod.py +232 -0
- esolangs/tools/boolean/dimensional.py +184 -0
- esolangs/tools/boolean/examples.py +507 -0
- esolangs/tools/boolean/helpers.py +173 -0
- esolangs/tools/boolean/other.py +1328 -0
- esolangs/tools/boolean/parameterized.py +899 -0
- esolangs/tools/boolean/register.py +542 -0
- esolangs/tools/boolean/rotfuck.py +219 -0
- esolangs/tools/boolean/six_five.py +248 -0
- esolangs/tools/boolean/stack.py +255 -0
- esolangs/tools/boolean/streetcode.py +169 -0
- esolangs/tools/boolean/tape.py +841 -0
- esolangs/tools/boolean/wii2d.py +537 -0
- esolangs/tools/boolean/ztoalc_l.py +182 -0
- esolangs/tools/laserfuck_layout.py +180 -0
- esolangs/tools/text/__init__.py +125 -0
- esolangs/tools/text/__main__.py +9 -0
- esolangs/tools/text/helpers.py +156 -0
- esolangs/tools/text/other.py +1650 -0
- esolangs/tools/text/register.py +351 -0
- esolangs/tools/text/stack.py +28 -0
- esolangs/tools/text/tape.py +482 -0
- esolangs/tools/transpilers.py +1074 -0
- esolangs/tools/wrap.py +605 -0
- esolangs/tools/ztoalc_starts.py +35 -0
- esolangs/vm.py +1966 -0
- esolangs-0.1.0.dist-info/METADATA +455 -0
- esolangs-0.1.0.dist-info/RECORD +129 -0
- esolangs-0.1.0.dist-info/WHEEL +5 -0
- esolangs-0.1.0.dist-info/entry_points.txt +2 -0
- esolangs-0.1.0.dist-info/licenses/LICENSE +674 -0
- esolangs-0.1.0.dist-info/top_level.txt +1 -0
esolangs/__init__.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Public API for the esolangs package.
|
|
2
|
+
|
|
3
|
+
Provides ``generate`` (produce a program that prints a text), ``run``
|
|
4
|
+
(execute a program through an interpreter), ``make_vm`` (a step-and-inspect
|
|
5
|
+
wrapper around the step-capable interpreters), ``make_debugger`` (a
|
|
6
|
+
breakpoint/watch layer over the VM), ``transpile`` (rewrite a
|
|
7
|
+
program between languages), ``describe`` (a structured language summary),
|
|
8
|
+
and ``list_languages``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import importlib
|
|
12
|
+
import pathlib
|
|
13
|
+
import signal
|
|
14
|
+
import threading
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from esolangs.debug import Debugger, make_debugger
|
|
19
|
+
from esolangs.exceptions import (
|
|
20
|
+
HaltError,
|
|
21
|
+
UnknownLanguageError,
|
|
22
|
+
UnsupportedTranspilationError,
|
|
23
|
+
)
|
|
24
|
+
from esolangs.interpreters.io import ScriptedIO
|
|
25
|
+
from esolangs.registry import GENERATORS, LANGUAGES, RUNNERS
|
|
26
|
+
from esolangs.tools.boolean import BOOLEAN
|
|
27
|
+
from esolangs.tools.transpilers import TRANSPILERS
|
|
28
|
+
from esolangs.tools.wrap import takes_width, wrap_program
|
|
29
|
+
from esolangs.vm import VM, make_vm
|
|
30
|
+
|
|
31
|
+
_EXAMPLES = pathlib.Path(__file__).resolve().parents[2] / "examples"
|
|
32
|
+
|
|
33
|
+
# Interpreter module family -> state model name.
|
|
34
|
+
_STATE_MODELS = {
|
|
35
|
+
"register_based": "register",
|
|
36
|
+
"tape_based": "tape",
|
|
37
|
+
"stack_based": "stack",
|
|
38
|
+
"grid_based": "grid",
|
|
39
|
+
"queue_based": "queue",
|
|
40
|
+
"other": "other",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def generate(language: str, text: str, width: int | None = None) -> str:
|
|
45
|
+
"""Return a program in ``language`` that prints ``text``.
|
|
46
|
+
|
|
47
|
+
``width`` bounds the program to that many columns for readability;
|
|
48
|
+
:data:`esolangs.tools.wrap.DEFAULT_WIDTH` is the conventional choice.
|
|
49
|
+
The default of ``None`` asks for no bound, so a caller that does not
|
|
50
|
+
want one gets exactly what the generator has always produced.
|
|
51
|
+
|
|
52
|
+
Most languages honour it by *wrapping* the finished program, breaking
|
|
53
|
+
only between whole tokens so it still means the same thing. A few build
|
|
54
|
+
a shape rather than a line -- Clockwise lays its code around a
|
|
55
|
+
rectangle's perimeter -- and cannot be reflowed after the fact; those
|
|
56
|
+
generators take the width themselves and lay the program out to fit.
|
|
57
|
+
|
|
58
|
+
A language whose newlines are semantic (the 2D grid languages) or that
|
|
59
|
+
rejects them outright (NoComment) ignores ``width`` rather than raising,
|
|
60
|
+
so one width can be passed across every language.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
fn = GENERATORS[language]
|
|
64
|
+
except KeyError:
|
|
65
|
+
raise UnknownLanguageError(language) from None
|
|
66
|
+
if width is not None and takes_width(fn):
|
|
67
|
+
return str(fn(text, width))
|
|
68
|
+
return wrap_program(str(fn(text)), LANGUAGES[language].id, width)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def run(
|
|
72
|
+
language: str,
|
|
73
|
+
program: str,
|
|
74
|
+
stdin: str = "",
|
|
75
|
+
timeout: float | None = None,
|
|
76
|
+
) -> str:
|
|
77
|
+
"""Execute ``program`` and return its output.
|
|
78
|
+
|
|
79
|
+
Input is fed to the program line by line from ``stdin``. ``timeout``
|
|
80
|
+
bounds execution wall-clock: after ``timeout`` seconds the run raises
|
|
81
|
+
:class:`HaltError`. The guard uses ``SIGALRM``, so it requires a Unix
|
|
82
|
+
main thread; elsewhere a ``timeout`` raises :class:`ValueError`.
|
|
83
|
+
"""
|
|
84
|
+
if timeout is not None and timeout <= 0:
|
|
85
|
+
raise ValueError(f"timeout must be positive, got {timeout}")
|
|
86
|
+
try:
|
|
87
|
+
module, split, kwargs = RUNNERS[language]
|
|
88
|
+
except KeyError:
|
|
89
|
+
raise UnknownLanguageError(language) from None
|
|
90
|
+
run_fn = importlib.import_module("esolangs.interpreters." + module).run
|
|
91
|
+
io_obj = ScriptedIO(stdin)
|
|
92
|
+
program_args: str | list[str] = program.splitlines() if split else program
|
|
93
|
+
_run(run_fn, program_args, io_obj, timeout, dict(kwargs))
|
|
94
|
+
return io_obj.getvalue()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _run(
|
|
98
|
+
run_fn: Callable[..., Any],
|
|
99
|
+
program: str | list[str],
|
|
100
|
+
io_obj: ScriptedIO,
|
|
101
|
+
timeout: float | None,
|
|
102
|
+
kwargs: dict[str, int],
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Run ``run_fn``, applying the wall-clock ``timeout`` guard when set."""
|
|
105
|
+
if timeout is None:
|
|
106
|
+
run_fn(program, io_obj, **kwargs)
|
|
107
|
+
elif threading.current_thread() is threading.main_thread() and hasattr(
|
|
108
|
+
signal, "SIGALRM"
|
|
109
|
+
):
|
|
110
|
+
_run_timed_signal(run_fn, program, io_obj, timeout, kwargs)
|
|
111
|
+
else:
|
|
112
|
+
raise ValueError("the timeout guard uses SIGALRM and needs a Unix main thread")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _run_timed_signal(
|
|
116
|
+
run_fn: Callable[..., Any],
|
|
117
|
+
program: str | list[str],
|
|
118
|
+
io_obj: ScriptedIO,
|
|
119
|
+
timeout: float,
|
|
120
|
+
kwargs: dict[str, int],
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Run ``run_fn`` under a ``SIGALRM`` wall-clock guard (main thread only)."""
|
|
123
|
+
|
|
124
|
+
def _timeout_handler(_signum: int, _frame: object) -> None:
|
|
125
|
+
# coverage cannot trace a raise inside a signal handler
|
|
126
|
+
raise HaltError(
|
|
127
|
+
f"execution exceeded the {timeout}-second timeout"
|
|
128
|
+
) # pragma: no cover
|
|
129
|
+
|
|
130
|
+
old = signal.signal(signal.SIGALRM, _timeout_handler)
|
|
131
|
+
signal.setitimer(signal.ITIMER_REAL, timeout)
|
|
132
|
+
try:
|
|
133
|
+
run_fn(program, io_obj, **kwargs)
|
|
134
|
+
finally:
|
|
135
|
+
signal.setitimer(signal.ITIMER_REAL, 0)
|
|
136
|
+
signal.signal(signal.SIGALRM, old)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def describe(language: str) -> dict[str, object]:
|
|
140
|
+
"""Return a structured description of ``language``.
|
|
141
|
+
|
|
142
|
+
The summary carries the state model (derived from the interpreter's
|
|
143
|
+
module family), whether the language has text and boolean generators,
|
|
144
|
+
its transpilers, its example programs, and its esolangs.org page.
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
lang = LANGUAGES[language]
|
|
148
|
+
except KeyError:
|
|
149
|
+
raise UnknownLanguageError(language) from None
|
|
150
|
+
module = RUNNERS.get(language)
|
|
151
|
+
family = module[0].split(".")[0] if module else None
|
|
152
|
+
examples = sorted(
|
|
153
|
+
str(p.relative_to(_EXAMPLES.parent)) for p in _EXAMPLES.glob(f"*/{lang.id}.txt")
|
|
154
|
+
)
|
|
155
|
+
transpilers = sorted(
|
|
156
|
+
(source, target)
|
|
157
|
+
for (source, target) in TRANSPILERS
|
|
158
|
+
if source == language or target == language
|
|
159
|
+
)
|
|
160
|
+
return {
|
|
161
|
+
"name": language,
|
|
162
|
+
"id": lang.id,
|
|
163
|
+
"state_model": _STATE_MODELS.get(family) if family else None,
|
|
164
|
+
"interpreter": lang.interpreter,
|
|
165
|
+
"text_generator": lang.generator is not None,
|
|
166
|
+
"boolean_generator": language in BOOLEAN,
|
|
167
|
+
"transpilers": transpilers,
|
|
168
|
+
"examples": examples,
|
|
169
|
+
"wiki_url": f"https://esolangs.org/wiki/{language.replace(' ', '_')}",
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def transpile(source: str, target: str, program: str, **kwargs: int) -> str:
|
|
174
|
+
"""Rewrite a ``program`` in ``source`` into an equivalent one in ``target``.
|
|
175
|
+
|
|
176
|
+
Transpilers with extra options accept them as keyword arguments (for
|
|
177
|
+
example ``size`` when targeting Circlefuck).
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
fn = TRANSPILERS[(source, target)]
|
|
181
|
+
except KeyError:
|
|
182
|
+
raise UnsupportedTranspilationError(source, target) from None
|
|
183
|
+
return fn(program, **kwargs)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def list_languages() -> list[str]:
|
|
187
|
+
"""Return the supported language names, sorted."""
|
|
188
|
+
return sorted(LANGUAGES)
|
esolangs/__main__.py
ADDED
esolangs/cli.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Command-line interface for the esolangs package.
|
|
2
|
+
|
|
3
|
+
Subcommands:
|
|
4
|
+
esolangs list list the supported languages
|
|
5
|
+
esolangs generate <language> <text> print a program that outputs text
|
|
6
|
+
(``--width N`` wraps it to N columns)
|
|
7
|
+
esolangs run <language> <file> run a program through its interpreter
|
|
8
|
+
esolangs transpile <from> <to> <file> rewrite a program into another language
|
|
9
|
+
|
|
10
|
+
For anything else (compilers, tools), invoke the module directly with
|
|
11
|
+
``python -m``, e.g. ``python -m esolangs.compilers.unsquare``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from esolangs import generate, list_languages, run, transpile
|
|
17
|
+
from esolangs.tools.wrap import DEFAULT_WIDTH
|
|
18
|
+
|
|
19
|
+
USAGE = """usage: esolangs <command> [...]
|
|
20
|
+
|
|
21
|
+
commands:
|
|
22
|
+
list list the supported languages
|
|
23
|
+
generate [--width N] <language> <text>
|
|
24
|
+
print a program that outputs text
|
|
25
|
+
(--width wraps it for readability)
|
|
26
|
+
run <language> <file> run a program through its interpreter
|
|
27
|
+
transpile <from> <to> <file> rewrite a program into another language
|
|
28
|
+
|
|
29
|
+
examples:
|
|
30
|
+
esolangs list
|
|
31
|
+
esolangs generate Circlefuck "Hello, World!"
|
|
32
|
+
esolangs generate --width Polynomial "Hello, World!"
|
|
33
|
+
esolangs run Circlefuck hello.txt
|
|
34
|
+
esolangs transpile brainfuck Circlefuck hello.bf
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _fail(message: str) -> None:
|
|
39
|
+
sys.stderr.write(message + "\n")
|
|
40
|
+
sys.exit(2)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _is_int(value: str) -> bool:
|
|
44
|
+
"""Whether ``value`` parses as an integer."""
|
|
45
|
+
try:
|
|
46
|
+
int(value)
|
|
47
|
+
except ValueError:
|
|
48
|
+
return False
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _pop_width(rest: list[str]) -> tuple[list[str], int | None]:
|
|
53
|
+
"""Split a ``--width N`` (or ``--width=N``) option out of ``rest``.
|
|
54
|
+
|
|
55
|
+
Returns the remaining positional arguments and the width, or ``None``
|
|
56
|
+
when the option is absent -- which leaves the program on one line, the
|
|
57
|
+
output ``generate`` has always produced. A bare ``--width`` takes the
|
|
58
|
+
conventional :data:`DEFAULT_WIDTH`, so the common case needs no number;
|
|
59
|
+
an explicit value must be an integer, since silently reading the
|
|
60
|
+
language name as a width would generate the wrong thing.
|
|
61
|
+
"""
|
|
62
|
+
args: list[str] = []
|
|
63
|
+
width: int | None = None
|
|
64
|
+
i = 0
|
|
65
|
+
while i < len(rest):
|
|
66
|
+
arg = rest[i]
|
|
67
|
+
if arg == "--width":
|
|
68
|
+
following = rest[i + 1] if i + 1 < len(rest) else None
|
|
69
|
+
if following is None or not _is_int(following):
|
|
70
|
+
width = DEFAULT_WIDTH
|
|
71
|
+
i += 1
|
|
72
|
+
continue
|
|
73
|
+
value = following
|
|
74
|
+
i += 2
|
|
75
|
+
elif arg.startswith("--width="):
|
|
76
|
+
value = arg.split("=", 1)[1]
|
|
77
|
+
i += 1
|
|
78
|
+
else:
|
|
79
|
+
args.append(arg)
|
|
80
|
+
i += 1
|
|
81
|
+
continue
|
|
82
|
+
try:
|
|
83
|
+
width = int(value)
|
|
84
|
+
except ValueError:
|
|
85
|
+
_fail(f"--width must be an integer, got {value!r}")
|
|
86
|
+
if width is not None and width <= 0:
|
|
87
|
+
_fail(f"--width must be positive, got {width}")
|
|
88
|
+
return args, width
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def main() -> None:
|
|
92
|
+
"""Dispatch the ``esolangs`` subcommands."""
|
|
93
|
+
argv = sys.argv[1:]
|
|
94
|
+
if not argv:
|
|
95
|
+
sys.stderr.write(USAGE)
|
|
96
|
+
sys.exit(2)
|
|
97
|
+
|
|
98
|
+
cmd, rest = argv[0], argv[1:]
|
|
99
|
+
if cmd == "list":
|
|
100
|
+
for name in list_languages():
|
|
101
|
+
print(name)
|
|
102
|
+
elif cmd == "generate":
|
|
103
|
+
rest, width = _pop_width(rest)
|
|
104
|
+
if len(rest) < 2:
|
|
105
|
+
_fail("usage: esolangs generate [--width N] <language> <text>")
|
|
106
|
+
try:
|
|
107
|
+
program = generate(rest[0], rest[1], width)
|
|
108
|
+
except ValueError as exc:
|
|
109
|
+
_fail(str(exc))
|
|
110
|
+
print(program)
|
|
111
|
+
elif cmd == "run":
|
|
112
|
+
if len(rest) < 2:
|
|
113
|
+
_fail("usage: esolangs run <language> <program-file>")
|
|
114
|
+
language, path = rest[0], rest[1]
|
|
115
|
+
try:
|
|
116
|
+
with open(path) as f:
|
|
117
|
+
program = f.read()
|
|
118
|
+
except OSError as exc:
|
|
119
|
+
_fail(f"cannot read {path}: {exc}")
|
|
120
|
+
stdin = "" if sys.stdin.isatty() else sys.stdin.read()
|
|
121
|
+
try:
|
|
122
|
+
output = run(language, program, stdin)
|
|
123
|
+
except ValueError as exc:
|
|
124
|
+
_fail(str(exc))
|
|
125
|
+
sys.stdout.write(output)
|
|
126
|
+
elif cmd == "transpile":
|
|
127
|
+
if len(rest) < 3:
|
|
128
|
+
_fail("usage: esolangs transpile <from> <to> <program-file>")
|
|
129
|
+
source, target, path = rest[0], rest[1], rest[2]
|
|
130
|
+
try:
|
|
131
|
+
with open(path) as f:
|
|
132
|
+
program = f.read()
|
|
133
|
+
except OSError as exc:
|
|
134
|
+
_fail(f"cannot read {path}: {exc}")
|
|
135
|
+
try:
|
|
136
|
+
output = transpile(source, target, program)
|
|
137
|
+
except ValueError as exc:
|
|
138
|
+
_fail(str(exc))
|
|
139
|
+
sys.stdout.write(output)
|
|
140
|
+
else:
|
|
141
|
+
_fail(f"unknown command: {cmd}\n\n{USAGE}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Compilers that turn esolang programs into RISC-V assembly."""
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Shared RISC-V assembly fragments for the OISC-style compilers.
|
|
2
|
+
|
|
3
|
+
decleq, S*bleq, and AddSubJump all compile to a fetch-decode-execute loop
|
|
4
|
+
over a ``.data`` array of 8-byte cells (their jump targets are computed at
|
|
5
|
+
runtime, so unlike the token-unrolling compilers they can't emit static
|
|
6
|
+
per-token blocks). These three routines -- byte I/O and the final
|
|
7
|
+
dword-per-row memory dump -- are copy-pasted identically between them;
|
|
8
|
+
keeping one copy here is what the ``duplicate-code`` check in
|
|
9
|
+
``scripts/verify.py`` enforces.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
GETBYTE = (
|
|
13
|
+
"# getbyte() -> a0; reads one byte of stdin, halts the program at EOF\n"
|
|
14
|
+
"# (matching the interpreter's EOFError, which unwinds the whole run)\n"
|
|
15
|
+
"getbyte:\n"
|
|
16
|
+
" addi sp, sp, -16\n"
|
|
17
|
+
" sd ra, 8(sp)\n"
|
|
18
|
+
" li a7, 63\n"
|
|
19
|
+
" li a0, 0\n"
|
|
20
|
+
" mv a1, sp\n"
|
|
21
|
+
" li a2, 1\n"
|
|
22
|
+
" ecall\n"
|
|
23
|
+
" blez a0, .halt\n"
|
|
24
|
+
" lbu a0, 0(sp)\n"
|
|
25
|
+
" ld ra, 8(sp)\n"
|
|
26
|
+
" addi sp, sp, 16\n"
|
|
27
|
+
" ret\n"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
READ_BYTE_OR_EOF = (
|
|
31
|
+
" addi sp, sp, -16\n"
|
|
32
|
+
" sd ra, 8(sp)\n"
|
|
33
|
+
" li a7, 63\n"
|
|
34
|
+
" li a0, 0\n"
|
|
35
|
+
" mv a1, sp\n"
|
|
36
|
+
" li a2, 1\n"
|
|
37
|
+
" ecall\n"
|
|
38
|
+
" blez a0, .eof\n"
|
|
39
|
+
" lbu a0, 0(sp)\n"
|
|
40
|
+
" ld ra, 8(sp)\n"
|
|
41
|
+
" addi sp, sp, 16\n"
|
|
42
|
+
" ret\n"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
PUTBYTE = (
|
|
46
|
+
"# putbyte(value: a0) -- write the low byte to stdout\n"
|
|
47
|
+
"putbyte:\n"
|
|
48
|
+
" addi sp, sp, -16\n"
|
|
49
|
+
" sd ra, 8(sp)\n"
|
|
50
|
+
" andi t0, a0, 0xff\n"
|
|
51
|
+
" sb t0, 0(sp)\n"
|
|
52
|
+
" li a7, 64\n"
|
|
53
|
+
" li a0, 1\n"
|
|
54
|
+
" mv a1, sp\n"
|
|
55
|
+
" li a2, 1\n"
|
|
56
|
+
" ecall\n"
|
|
57
|
+
" ld ra, 8(sp)\n"
|
|
58
|
+
" addi sp, sp, 16\n"
|
|
59
|
+
" ret\n"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
MUL32 = (
|
|
64
|
+
"# a0 *= a1 (unsigned 32-bit), result in a0\n"
|
|
65
|
+
"mul32:\n"
|
|
66
|
+
"\tmv t4, a0\n"
|
|
67
|
+
"\tli a0, 0\n"
|
|
68
|
+
".mul_loop:\n"
|
|
69
|
+
"\tandi t5, a1, 1\n"
|
|
70
|
+
"\tbeqz t5, .mul_skip\n"
|
|
71
|
+
"\tadd a0, a0, t4\n"
|
|
72
|
+
".mul_skip:\n"
|
|
73
|
+
"\tslli t4, t4, 1\n"
|
|
74
|
+
"\tsrli a1, a1, 1\n"
|
|
75
|
+
"\tbnez a1, .mul_loop\n"
|
|
76
|
+
"\tret"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def start_preamble(num_cells_used: int) -> str:
|
|
81
|
+
"""``.text``/``_start``: point ``s1`` at ``mem``, ``s2``/``s3`` at ip/length.
|
|
82
|
+
|
|
83
|
+
``num_cells_used`` is the compiled program's cell count (``n``), not
|
|
84
|
+
the fixed buffer size -- ``s3`` bounds the fetch-decode-execute loop
|
|
85
|
+
to the program itself, with the rest of the buffer as zeroed scratch.
|
|
86
|
+
"""
|
|
87
|
+
return (
|
|
88
|
+
" .text\n"
|
|
89
|
+
" .global _start\n"
|
|
90
|
+
"_start:\n"
|
|
91
|
+
" la s1, mem\n"
|
|
92
|
+
" li s2, 0\n"
|
|
93
|
+
f" li s3, {num_cells_used}\n"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def cell_load_or_zero(num_cells: int) -> str:
|
|
98
|
+
"""Range-check ``a0`` against the cell buffer, load it or return 0.
|
|
99
|
+
|
|
100
|
+
Expects ``s1`` = memory base. Falls through from a caller that has
|
|
101
|
+
already ruled out its own special addresses, so this only needs the
|
|
102
|
+
generic bounds check every ``read_cell`` ends with.
|
|
103
|
+
"""
|
|
104
|
+
return (
|
|
105
|
+
" bltz a0, .zero_ret\n"
|
|
106
|
+
f" li t0, {num_cells}\n"
|
|
107
|
+
" bge a0, t0, .zero_ret\n"
|
|
108
|
+
" slli t0, a0, 3\n"
|
|
109
|
+
" add t0, s1, t0\n"
|
|
110
|
+
" ld a0, 0(t0)\n"
|
|
111
|
+
" ret\n"
|
|
112
|
+
".zero_ret:\n"
|
|
113
|
+
" li a0, 0\n"
|
|
114
|
+
" ret\n"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cell_store_or_drop(num_cells: int) -> str:
|
|
119
|
+
"""Range-check ``a0`` against the cell buffer and store ``a1``, or drop.
|
|
120
|
+
|
|
121
|
+
Expects ``s1`` = memory base. Like :func:`cell_load_or_zero`, this is
|
|
122
|
+
the generic bounds check a ``write_cell`` falls through to after its
|
|
123
|
+
own special addresses are ruled out.
|
|
124
|
+
"""
|
|
125
|
+
return (
|
|
126
|
+
" bltz a0, .no_write\n"
|
|
127
|
+
f" li t0, {num_cells}\n"
|
|
128
|
+
" bge a0, t0, .no_write\n"
|
|
129
|
+
" slli t0, a0, 3\n"
|
|
130
|
+
" add t0, s1, t0\n"
|
|
131
|
+
" sd a1, 0(t0)\n"
|
|
132
|
+
".no_write:\n"
|
|
133
|
+
" ret\n"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def dump_cells(cells: list[int], num_cells: int) -> str:
|
|
138
|
+
"""Render ``.data``/``.align``/dword rows for a fixed-size cell buffer."""
|
|
139
|
+
res = " .data\n .align 3\nmem:\n"
|
|
140
|
+
for i in range(0, num_cells, 8):
|
|
141
|
+
row = cells[i : i + 8]
|
|
142
|
+
res += " .dword " + ", ".join(str(v) for v in row) + "\n"
|
|
143
|
+
return res
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Compiler that turns AddSubJump programs into RISC-V Linux assembly."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from esolangs.compilers._riscv_common import (
|
|
6
|
+
READ_BYTE_OR_EOF,
|
|
7
|
+
cell_load_or_zero,
|
|
8
|
+
cell_store_or_drop,
|
|
9
|
+
dump_cells,
|
|
10
|
+
start_preamble,
|
|
11
|
+
)
|
|
12
|
+
from esolangs.interpreters.memory import parse_int_memory as _parse
|
|
13
|
+
|
|
14
|
+
# Special addresses, matching esolangs.interpreters.register_based.addsubjump.
|
|
15
|
+
_IO, _CF, _ZF, _NF, _OF, _ONE, _ZERO, _NEG, _FUM = -1, -2, -3, -4, -5, -6, -7, -8, -9
|
|
16
|
+
|
|
17
|
+
# Fixed-size cell buffer: the interpreter's memory grows on out-of-range
|
|
18
|
+
# writes, but a compiled program gets a generous preallocated word array
|
|
19
|
+
# instead (the same tradeoff RAM0's compiler makes for its 256-cell RAM).
|
|
20
|
+
_CELLS = 65536
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def comp(code: str) -> str:
|
|
24
|
+
"""Compile an AddSubJump program to RISC-V assembly.
|
|
25
|
+
|
|
26
|
+
AddSubJump's memory is self-modifying and its jump target (``*c``) is
|
|
27
|
+
computed at runtime, so unlike the other compilers this cannot unroll
|
|
28
|
+
into static per-token blocks: it emits a real fetch-decode-execute loop
|
|
29
|
+
over a ``.data`` array of 8-byte cells. Registers: ``s1`` = memory base,
|
|
30
|
+
``s2`` = ip (in cells), ``s3`` = memory length (in cells), ``s4`` = flag
|
|
31
|
+
update mode, ``s5``/``s6`` = zero/negative flags. ``read_cell`` and
|
|
32
|
+
``write_cell`` centralize the special-address handling (``-1`` I/O,
|
|
33
|
+
``-2``..``-5`` flags, ``-6``/``-7``/``-8`` constants, ``-9`` flag mode)
|
|
34
|
+
that both operand fetch and the write-back share.
|
|
35
|
+
"""
|
|
36
|
+
cells = _parse(code)
|
|
37
|
+
n = len(cells)
|
|
38
|
+
if n < _CELLS:
|
|
39
|
+
cells = cells + [0] * (_CELLS - n)
|
|
40
|
+
|
|
41
|
+
res = (
|
|
42
|
+
start_preamble(n) + " li s4, 0\n"
|
|
43
|
+
" li s5, 0\n"
|
|
44
|
+
" li s6, 0\n"
|
|
45
|
+
".loop:\n"
|
|
46
|
+
" bltz s2, .halt\n"
|
|
47
|
+
" bge s2, s3, .halt\n"
|
|
48
|
+
" slli t0, s2, 3\n"
|
|
49
|
+
" add t0, s1, t0\n"
|
|
50
|
+
" ld a0, 0(t0)\n" # a
|
|
51
|
+
" ld a1, 8(t0)\n" # b
|
|
52
|
+
" ld a2, 16(t0)\n" # c
|
|
53
|
+
" ld a3, 24(t0)\n" # d
|
|
54
|
+
" mv t1, a0\n" # save a (write target) across calls
|
|
55
|
+
" mv t2, a2\n" # save c (jump target)
|
|
56
|
+
" mv a0, a3\n"
|
|
57
|
+
" call read_cell\n" # vd = read(d)
|
|
58
|
+
" mv t3, a0\n"
|
|
59
|
+
" mv a0, a1\n"
|
|
60
|
+
" call read_cell\n" # vb = read(b)
|
|
61
|
+
" mv t4, a0\n" # vb
|
|
62
|
+
" li t0, -1\n"
|
|
63
|
+
" bne t1, t0, .not_io\n"
|
|
64
|
+
" mv a0, t4\n" # a == -1: write vb straight to I/O
|
|
65
|
+
" j .have_new\n"
|
|
66
|
+
".not_io:\n"
|
|
67
|
+
" mv a0, t1\n"
|
|
68
|
+
" call read_cell\n" # read(a)
|
|
69
|
+
" blez t3, .add\n"
|
|
70
|
+
" sub a0, a0, t4\n"
|
|
71
|
+
" j .have_new\n"
|
|
72
|
+
".add:\n"
|
|
73
|
+
" add a0, a0, t4\n"
|
|
74
|
+
".have_new:\n"
|
|
75
|
+
" mv t5, a0\n" # new
|
|
76
|
+
" mv a1, t5\n"
|
|
77
|
+
" mv a0, t1\n"
|
|
78
|
+
" call write_cell\n"
|
|
79
|
+
" beqz s4, .no_flags\n"
|
|
80
|
+
" seqz s5, t5\n"
|
|
81
|
+
" sltz s6, t5\n"
|
|
82
|
+
".no_flags:\n"
|
|
83
|
+
" mv a0, t2\n"
|
|
84
|
+
" call read_cell\n" # ip = read(c)
|
|
85
|
+
" mv s2, a0\n"
|
|
86
|
+
" j .loop\n"
|
|
87
|
+
".halt:\n"
|
|
88
|
+
" li a0, 0\n"
|
|
89
|
+
" li a7, 93\n"
|
|
90
|
+
" ecall\n"
|
|
91
|
+
"\n"
|
|
92
|
+
"# read_cell(addr: a0) -> a0\n"
|
|
93
|
+
"read_cell:\n"
|
|
94
|
+
f" li t0, {_IO}\n"
|
|
95
|
+
" bne a0, t0, 1f\n" + READ_BYTE_OR_EOF + "1:\n"
|
|
96
|
+
f" li t0, {_CF}\n"
|
|
97
|
+
" bne a0, t0, 2f\n"
|
|
98
|
+
" li a0, 0\n"
|
|
99
|
+
" ret\n"
|
|
100
|
+
"2:\n"
|
|
101
|
+
f" li t0, {_ZF}\n"
|
|
102
|
+
" bne a0, t0, 3f\n"
|
|
103
|
+
" mv a0, s5\n"
|
|
104
|
+
" ret\n"
|
|
105
|
+
"3:\n"
|
|
106
|
+
f" li t0, {_NF}\n"
|
|
107
|
+
" bne a0, t0, 4f\n"
|
|
108
|
+
" mv a0, s6\n"
|
|
109
|
+
" ret\n"
|
|
110
|
+
"4:\n"
|
|
111
|
+
f" li t0, {_OF}\n"
|
|
112
|
+
" bne a0, t0, 5f\n"
|
|
113
|
+
" li a0, 0\n"
|
|
114
|
+
" ret\n"
|
|
115
|
+
"5:\n"
|
|
116
|
+
f" li t0, {_ONE}\n"
|
|
117
|
+
" bne a0, t0, 6f\n"
|
|
118
|
+
" li a0, 1\n"
|
|
119
|
+
" ret\n"
|
|
120
|
+
"6:\n"
|
|
121
|
+
f" li t0, {_ZERO}\n"
|
|
122
|
+
" bne a0, t0, 7f\n"
|
|
123
|
+
" li a0, 0\n"
|
|
124
|
+
" ret\n"
|
|
125
|
+
"7:\n"
|
|
126
|
+
f" li t0, {_NEG}\n"
|
|
127
|
+
" bne a0, t0, 8f\n"
|
|
128
|
+
" li a0, -1\n"
|
|
129
|
+
" ret\n"
|
|
130
|
+
"8:\n"
|
|
131
|
+
f" li t0, {_FUM}\n"
|
|
132
|
+
" bne a0, t0, 9f\n"
|
|
133
|
+
" mv a0, s4\n"
|
|
134
|
+
" ret\n"
|
|
135
|
+
"9:\n" + cell_load_or_zero(_CELLS) + ".eof:\n"
|
|
136
|
+
" li a0, 1\n"
|
|
137
|
+
" li a7, 93\n"
|
|
138
|
+
" ecall\n"
|
|
139
|
+
"\n"
|
|
140
|
+
"# write_cell(addr: a0, value: a1)\n"
|
|
141
|
+
"write_cell:\n"
|
|
142
|
+
f" li t0, {_IO}\n"
|
|
143
|
+
" bne a0, t0, 1f\n"
|
|
144
|
+
" addi sp, sp, -16\n"
|
|
145
|
+
" sd ra, 8(sp)\n"
|
|
146
|
+
" andi t0, a1, 0xff\n"
|
|
147
|
+
" sb t0, 0(sp)\n"
|
|
148
|
+
" li a7, 64\n"
|
|
149
|
+
" li a0, 1\n"
|
|
150
|
+
" mv a1, sp\n"
|
|
151
|
+
" li a2, 1\n"
|
|
152
|
+
" ecall\n"
|
|
153
|
+
" ld ra, 8(sp)\n"
|
|
154
|
+
" addi sp, sp, 16\n"
|
|
155
|
+
" ret\n"
|
|
156
|
+
"1:\n"
|
|
157
|
+
f" li t0, {_FUM}\n"
|
|
158
|
+
" bne a0, t0, 2f\n"
|
|
159
|
+
" mv s4, a1\n"
|
|
160
|
+
" ret\n"
|
|
161
|
+
"2:\n"
|
|
162
|
+
f" li t0, {_CF}\n" # -8..-2 (NEG..CF): other specials are read-only
|
|
163
|
+
" bgt a0, t0, 3f\n"
|
|
164
|
+
f" li t1, {_NEG}\n"
|
|
165
|
+
" blt a0, t1, 3f\n"
|
|
166
|
+
" j .no_write\n"
|
|
167
|
+
"3:\n" + cell_store_or_drop(_CELLS)
|
|
168
|
+
)
|
|
169
|
+
return res + dump_cells(cells, _CELLS)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
if len(sys.argv) > 1:
|
|
174
|
+
with open(sys.argv[1]) as f:
|
|
175
|
+
data = f.read()
|
|
176
|
+
|
|
177
|
+
with open("output.asm", "w") as f:
|
|
178
|
+
f.write(comp(data))
|