+21
.tangled/workflows/clippy.yaml
+21
.tangled/workflows/clippy.yaml
···
1
+
when:
2
+
- event:
3
+
- push
4
+
- pull_request
5
+
branch:
6
+
- main
7
+
8
+
engine: nixery
9
+
10
+
dependencies:
11
+
nixpkgs:
12
+
- cargo
13
+
- clang
14
+
- clippy
15
+
16
+
steps:
17
+
- name: Run clippy
18
+
environment:
19
+
RUSTFLAGS: "-Dwarnings"
20
+
command: |
21
+
cargo clippy --all-targets --all-features
+18
.tangled/workflows/unit-tests.yaml
+18
.tangled/workflows/unit-tests.yaml
+103
-1
Cargo.lock
+103
-1
Cargo.lock
···
3
3
version = 3
4
4
5
5
[[package]]
6
+
name = "beef"
7
+
version = "0.5.2"
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
+
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
10
+
11
+
[[package]]
12
+
name = "fnv"
13
+
version = "1.0.7"
14
+
source = "registry+https://github.com/rust-lang/crates.io-index"
15
+
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
16
+
17
+
[[package]]
18
+
name = "lazy_static"
19
+
version = "1.5.0"
20
+
source = "registry+https://github.com/rust-lang/crates.io-index"
21
+
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
22
+
23
+
[[package]]
24
+
name = "logos"
25
+
version = "0.14.4"
26
+
source = "registry+https://github.com/rust-lang/crates.io-index"
27
+
checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458"
28
+
dependencies = [
29
+
"logos-derive",
30
+
]
31
+
32
+
[[package]]
33
+
name = "logos-codegen"
34
+
version = "0.14.4"
35
+
source = "registry+https://github.com/rust-lang/crates.io-index"
36
+
checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f"
37
+
dependencies = [
38
+
"beef",
39
+
"fnv",
40
+
"lazy_static",
41
+
"proc-macro2",
42
+
"quote",
43
+
"regex-syntax",
44
+
"syn",
45
+
]
46
+
47
+
[[package]]
48
+
name = "logos-derive"
49
+
version = "0.14.4"
50
+
source = "registry+https://github.com/rust-lang/crates.io-index"
51
+
checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d"
52
+
dependencies = [
53
+
"logos-codegen",
54
+
]
55
+
56
+
[[package]]
57
+
name = "proc-macro2"
58
+
version = "1.0.101"
59
+
source = "registry+https://github.com/rust-lang/crates.io-index"
60
+
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
61
+
dependencies = [
62
+
"unicode-ident",
63
+
]
64
+
65
+
[[package]]
66
+
name = "quote"
67
+
version = "1.0.40"
68
+
source = "registry+https://github.com/rust-lang/crates.io-index"
69
+
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
70
+
dependencies = [
71
+
"proc-macro2",
72
+
]
73
+
74
+
[[package]]
75
+
name = "regex-syntax"
76
+
version = "0.8.6"
77
+
source = "registry+https://github.com/rust-lang/crates.io-index"
78
+
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
79
+
80
+
[[package]]
81
+
name = "smallvec"
82
+
version = "1.15.1"
83
+
source = "registry+https://github.com/rust-lang/crates.io-index"
84
+
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
85
+
86
+
[[package]]
87
+
name = "syn"
88
+
version = "2.0.106"
89
+
source = "registry+https://github.com/rust-lang/crates.io-index"
90
+
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
91
+
dependencies = [
92
+
"proc-macro2",
93
+
"quote",
94
+
"unicode-ident",
95
+
]
96
+
97
+
[[package]]
6
98
name = "um"
7
-
version = "0.1.0"
99
+
version = "0.2.0"
100
+
dependencies = [
101
+
"logos",
102
+
"smallvec",
103
+
]
104
+
105
+
[[package]]
106
+
name = "unicode-ident"
107
+
version = "1.0.19"
108
+
source = "registry+https://github.com/rust-lang/crates.io-index"
109
+
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
+21
-3
Cargo.toml
+21
-3
Cargo.toml
···
1
1
[package]
2
2
name = "um"
3
-
version = "0.1.0"
3
+
version = "0.2.0"
4
4
edition = "2021"
5
+
authors = ["tjh <14987462+thomhayward@users.noreply.github.com>"]
6
+
license = "GPL-3.0-only"
7
+
default-run = "um"
8
+
rust-version = "1.74.1"
9
+
10
+
[dependencies]
11
+
smallvec = { version = "1.13.2" }
12
+
logos = { version = "0.14.2", optional = true }
5
13
6
14
[features]
7
-
default = ["reclaim-memory"]
8
-
reclaim-memory = []
15
+
default = []
16
+
asm = ["dep:logos"]
17
+
18
+
[profile.release]
19
+
lto = "fat"
20
+
codegen-units = 1
21
+
22
+
23
+
[[bin]]
24
+
name = "uasm"
25
+
path = "src/bin/uasm.rs"
26
+
required-features = ["asm"]
+675
LICENSE.md
+675
LICENSE.md
···
1
+
# GNU GENERAL PUBLIC LICENSE
2
+
3
+
Version 3, 29 June 2007
4
+
5
+
Copyright (C) 2007 Free Software Foundation, Inc.
6
+
<https://fsf.org/>
7
+
8
+
Everyone is permitted to copy and distribute verbatim copies of this
9
+
license document, but changing it is not allowed.
10
+
11
+
## Preamble
12
+
13
+
The GNU General Public License is a free, copyleft license for
14
+
software and other kinds of works.
15
+
16
+
The licenses for most software and other practical works are designed
17
+
to take away your freedom to share and change the works. By contrast,
18
+
the GNU General Public License is intended to guarantee your freedom
19
+
to share and change all versions of a program--to make sure it remains
20
+
free software for all its users. We, the Free Software Foundation, use
21
+
the GNU General Public License for most of our software; it applies
22
+
also to any other work released this way by its authors. You can apply
23
+
it to your programs, too.
24
+
25
+
When we speak of free software, we are referring to freedom, not
26
+
price. Our General Public Licenses are designed to make sure that you
27
+
have the freedom to distribute copies of free software (and charge for
28
+
them if you wish), that you receive source code or can get it if you
29
+
want it, that you can change the software or use pieces of it in new
30
+
free programs, and that you know you can do these things.
31
+
32
+
To protect your rights, we need to prevent others from denying you
33
+
these rights or asking you to surrender the rights. Therefore, you
34
+
have certain responsibilities if you distribute copies of the
35
+
software, or if you modify it: responsibilities to respect the freedom
36
+
of others.
37
+
38
+
For example, if you distribute copies of such a program, whether
39
+
gratis or for a fee, you must pass on to the recipients the same
40
+
freedoms that you received. You must make sure that they, too, receive
41
+
or can get the source code. And you must show them these terms so they
42
+
know their rights.
43
+
44
+
Developers that use the GNU GPL protect your rights with two steps:
45
+
(1) assert copyright on the software, and (2) offer you this License
46
+
giving you legal permission to copy, distribute and/or modify it.
47
+
48
+
For the developers' and authors' protection, the GPL clearly explains
49
+
that there is no warranty for this free software. For both users' and
50
+
authors' sake, the GPL requires that modified versions be marked as
51
+
changed, so that their problems will not be attributed erroneously to
52
+
authors of previous versions.
53
+
54
+
Some devices are designed to deny users access to install or run
55
+
modified versions of the software inside them, although the
56
+
manufacturer can do so. This is fundamentally incompatible with the
57
+
aim of protecting users' freedom to change the software. The
58
+
systematic pattern of such abuse occurs in the area of products for
59
+
individuals to use, which is precisely where it is most unacceptable.
60
+
Therefore, we have designed this version of the GPL to prohibit the
61
+
practice for those products. If such problems arise substantially in
62
+
other domains, we stand ready to extend this provision to those
63
+
domains in future versions of the GPL, as needed to protect the
64
+
freedom of users.
65
+
66
+
Finally, every program is threatened constantly by software patents.
67
+
States should not allow patents to restrict development and use of
68
+
software on general-purpose computers, but in those that do, we wish
69
+
to avoid the special danger that patents applied to a free program
70
+
could make it effectively proprietary. To prevent this, the GPL
71
+
assures that patents cannot be used to render the program non-free.
72
+
73
+
The precise terms and conditions for copying, distribution and
74
+
modification follow.
75
+
76
+
## TERMS AND CONDITIONS
77
+
78
+
### 0. Definitions.
79
+
80
+
"This License" refers to version 3 of the GNU General Public License.
81
+
82
+
"Copyright" also means copyright-like laws that apply to other kinds
83
+
of works, such as semiconductor masks.
84
+
85
+
"The Program" refers to any copyrightable work licensed under this
86
+
License. Each licensee is addressed as "you". "Licensees" and
87
+
"recipients" may be individuals or organizations.
88
+
89
+
To "modify" a work means to copy from or adapt all or part of the work
90
+
in a fashion requiring copyright permission, other than the making of
91
+
an exact copy. The resulting work is called a "modified version" of
92
+
the earlier work or a work "based on" the earlier work.
93
+
94
+
A "covered work" means either the unmodified Program or a work based
95
+
on the Program.
96
+
97
+
To "propagate" a work means to do anything with it that, without
98
+
permission, would make you directly or secondarily liable for
99
+
infringement under applicable copyright law, except executing it on a
100
+
computer or modifying a private copy. Propagation includes copying,
101
+
distribution (with or without modification), making available to the
102
+
public, and in some countries other activities as well.
103
+
104
+
To "convey" a work means any kind of propagation that enables other
105
+
parties to make or receive copies. Mere interaction with a user
106
+
through a computer network, with no transfer of a copy, is not
107
+
conveying.
108
+
109
+
An interactive user interface displays "Appropriate Legal Notices" to
110
+
the extent that it includes a convenient and prominently visible
111
+
feature that (1) displays an appropriate copyright notice, and (2)
112
+
tells the user that there is no warranty for the work (except to the
113
+
extent that warranties are provided), that licensees may convey the
114
+
work under this License, and how to view a copy of this License. If
115
+
the interface presents a list of user commands or options, such as a
116
+
menu, a prominent item in the list meets this criterion.
117
+
118
+
### 1. Source Code.
119
+
120
+
The "source code" for a work means the preferred form of the work for
121
+
making modifications to it. "Object code" means any non-source form of
122
+
a work.
123
+
124
+
A "Standard Interface" means an interface that either is an official
125
+
standard defined by a recognized standards body, or, in the case of
126
+
interfaces specified for a particular programming language, one that
127
+
is widely used among developers working in that language.
128
+
129
+
The "System Libraries" of an executable work include anything, other
130
+
than the work as a whole, that (a) is included in the normal form of
131
+
packaging a Major Component, but which is not part of that Major
132
+
Component, and (b) serves only to enable use of the work with that
133
+
Major Component, or to implement a Standard Interface for which an
134
+
implementation is available to the public in source code form. A
135
+
"Major Component", in this context, means a major essential component
136
+
(kernel, window system, and so on) of the specific operating system
137
+
(if any) on which the executable work runs, or a compiler used to
138
+
produce the work, or an object code interpreter used to run it.
139
+
140
+
The "Corresponding Source" for a work in object code form means all
141
+
the source code needed to generate, install, and (for an executable
142
+
work) run the object code and to modify the work, including scripts to
143
+
control those activities. However, it does not include the work's
144
+
System Libraries, or general-purpose tools or generally available free
145
+
programs which are used unmodified in performing those activities but
146
+
which are not part of the work. For example, Corresponding Source
147
+
includes interface definition files associated with source files for
148
+
the work, and the source code for shared libraries and dynamically
149
+
linked subprograms that the work is specifically designed to require,
150
+
such as by intimate data communication or control flow between those
151
+
subprograms and other parts of the work.
152
+
153
+
The Corresponding Source need not include anything that users can
154
+
regenerate automatically from other parts of the Corresponding Source.
155
+
156
+
The Corresponding Source for a work in source code form is that same
157
+
work.
158
+
159
+
### 2. Basic Permissions.
160
+
161
+
All rights granted under this License are granted for the term of
162
+
copyright on the Program, and are irrevocable provided the stated
163
+
conditions are met. This License explicitly affirms your unlimited
164
+
permission to run the unmodified Program. The output from running a
165
+
covered work is covered by this License only if the output, given its
166
+
content, constitutes a covered work. This License acknowledges your
167
+
rights of fair use or other equivalent, as provided by copyright law.
168
+
169
+
You may make, run and propagate covered works that you do not convey,
170
+
without conditions so long as your license otherwise remains in force.
171
+
You may convey covered works to others for the sole purpose of having
172
+
them make modifications exclusively for you, or provide you with
173
+
facilities for running those works, provided that you comply with the
174
+
terms of this License in conveying all material for which you do not
175
+
control copyright. Those thus making or running the covered works for
176
+
you must do so exclusively on your behalf, under your direction and
177
+
control, on terms that prohibit them from making any copies of your
178
+
copyrighted material outside their relationship with you.
179
+
180
+
Conveying under any other circumstances is permitted solely under the
181
+
conditions stated below. Sublicensing is not allowed; section 10 makes
182
+
it unnecessary.
183
+
184
+
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
185
+
186
+
No covered work shall be deemed part of an effective technological
187
+
measure under any applicable law fulfilling obligations under article
188
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
189
+
similar laws prohibiting or restricting circumvention of such
190
+
measures.
191
+
192
+
When you convey a covered work, you waive any legal power to forbid
193
+
circumvention of technological measures to the extent such
194
+
circumvention is effected by exercising rights under this License with
195
+
respect to the covered work, and you disclaim any intention to limit
196
+
operation or modification of the work as a means of enforcing, against
197
+
the work's users, your or third parties' legal rights to forbid
198
+
circumvention of technological measures.
199
+
200
+
### 4. Conveying Verbatim Copies.
201
+
202
+
You may convey verbatim copies of the Program's source code as you
203
+
receive it, in any medium, provided that you conspicuously and
204
+
appropriately publish on each copy an appropriate copyright notice;
205
+
keep intact all notices stating that this License and any
206
+
non-permissive terms added in accord with section 7 apply to the code;
207
+
keep intact all notices of the absence of any warranty; and give all
208
+
recipients a copy of this License along with the Program.
209
+
210
+
You may charge any price or no price for each copy that you convey,
211
+
and you may offer support or warranty protection for a fee.
212
+
213
+
### 5. Conveying Modified Source Versions.
214
+
215
+
You may convey a work based on the Program, or the modifications to
216
+
produce it from the Program, in the form of source code under the
217
+
terms of section 4, provided that you also meet all of these
218
+
conditions:
219
+
220
+
- a) The work must carry prominent notices stating that you modified
221
+
it, and giving a relevant date.
222
+
- b) The work must carry prominent notices stating that it is
223
+
released under this License and any conditions added under
224
+
section 7. This requirement modifies the requirement in section 4
225
+
to "keep intact all notices".
226
+
- c) You must license the entire work, as a whole, under this
227
+
License to anyone who comes into possession of a copy. This
228
+
License will therefore apply, along with any applicable section 7
229
+
additional terms, to the whole of the work, and all its parts,
230
+
regardless of how they are packaged. This License gives no
231
+
permission to license the work in any other way, but it does not
232
+
invalidate such permission if you have separately received it.
233
+
- d) If the work has interactive user interfaces, each must display
234
+
Appropriate Legal Notices; however, if the Program has interactive
235
+
interfaces that do not display Appropriate Legal Notices, your
236
+
work need not make them do so.
237
+
238
+
A compilation of a covered work with other separate and independent
239
+
works, which are not by their nature extensions of the covered work,
240
+
and which are not combined with it such as to form a larger program,
241
+
in or on a volume of a storage or distribution medium, is called an
242
+
"aggregate" if the compilation and its resulting copyright are not
243
+
used to limit the access or legal rights of the compilation's users
244
+
beyond what the individual works permit. Inclusion of a covered work
245
+
in an aggregate does not cause this License to apply to the other
246
+
parts of the aggregate.
247
+
248
+
### 6. Conveying Non-Source Forms.
249
+
250
+
You may convey a covered work in object code form under the terms of
251
+
sections 4 and 5, provided that you also convey the machine-readable
252
+
Corresponding Source under the terms of this License, in one of these
253
+
ways:
254
+
255
+
- a) Convey the object code in, or embodied in, a physical product
256
+
(including a physical distribution medium), accompanied by the
257
+
Corresponding Source fixed on a durable physical medium
258
+
customarily used for software interchange.
259
+
- b) Convey the object code in, or embodied in, a physical product
260
+
(including a physical distribution medium), accompanied by a
261
+
written offer, valid for at least three years and valid for as
262
+
long as you offer spare parts or customer support for that product
263
+
model, to give anyone who possesses the object code either (1) a
264
+
copy of the Corresponding Source for all the software in the
265
+
product that is covered by this License, on a durable physical
266
+
medium customarily used for software interchange, for a price no
267
+
more than your reasonable cost of physically performing this
268
+
conveying of source, or (2) access to copy the Corresponding
269
+
Source from a network server at no charge.
270
+
- c) Convey individual copies of the object code with a copy of the
271
+
written offer to provide the Corresponding Source. This
272
+
alternative is allowed only occasionally and noncommercially, and
273
+
only if you received the object code with such an offer, in accord
274
+
with subsection 6b.
275
+
- d) Convey the object code by offering access from a designated
276
+
place (gratis or for a charge), and offer equivalent access to the
277
+
Corresponding Source in the same way through the same place at no
278
+
further charge. You need not require recipients to copy the
279
+
Corresponding Source along with the object code. If the place to
280
+
copy the object code is a network server, the Corresponding Source
281
+
may be on a different server (operated by you or a third party)
282
+
that supports equivalent copying facilities, provided you maintain
283
+
clear directions next to the object code saying where to find the
284
+
Corresponding Source. Regardless of what server hosts the
285
+
Corresponding Source, you remain obligated to ensure that it is
286
+
available for as long as needed to satisfy these requirements.
287
+
- e) Convey the object code using peer-to-peer transmission,
288
+
provided you inform other peers where the object code and
289
+
Corresponding Source of the work are being offered to the general
290
+
public at no charge under subsection 6d.
291
+
292
+
A separable portion of the object code, whose source code is excluded
293
+
from the Corresponding Source as a System Library, need not be
294
+
included in conveying the object code work.
295
+
296
+
A "User Product" is either (1) a "consumer product", which means any
297
+
tangible personal property which is normally used for personal,
298
+
family, or household purposes, or (2) anything designed or sold for
299
+
incorporation into a dwelling. In determining whether a product is a
300
+
consumer product, doubtful cases shall be resolved in favor of
301
+
coverage. For a particular product received by a particular user,
302
+
"normally used" refers to a typical or common use of that class of
303
+
product, regardless of the status of the particular user or of the way
304
+
in which the particular user actually uses, or expects or is expected
305
+
to use, the product. A product is a consumer product regardless of
306
+
whether the product has substantial commercial, industrial or
307
+
non-consumer uses, unless such uses represent the only significant
308
+
mode of use of the product.
309
+
310
+
"Installation Information" for a User Product means any methods,
311
+
procedures, authorization keys, or other information required to
312
+
install and execute modified versions of a covered work in that User
313
+
Product from a modified version of its Corresponding Source. The
314
+
information must suffice to ensure that the continued functioning of
315
+
the modified object code is in no case prevented or interfered with
316
+
solely because modification has been made.
317
+
318
+
If you convey an object code work under this section in, or with, or
319
+
specifically for use in, a User Product, and the conveying occurs as
320
+
part of a transaction in which the right of possession and use of the
321
+
User Product is transferred to the recipient in perpetuity or for a
322
+
fixed term (regardless of how the transaction is characterized), the
323
+
Corresponding Source conveyed under this section must be accompanied
324
+
by the Installation Information. But this requirement does not apply
325
+
if neither you nor any third party retains the ability to install
326
+
modified object code on the User Product (for example, the work has
327
+
been installed in ROM).
328
+
329
+
The requirement to provide Installation Information does not include a
330
+
requirement to continue to provide support service, warranty, or
331
+
updates for a work that has been modified or installed by the
332
+
recipient, or for the User Product in which it has been modified or
333
+
installed. Access to a network may be denied when the modification
334
+
itself materially and adversely affects the operation of the network
335
+
or violates the rules and protocols for communication across the
336
+
network.
337
+
338
+
Corresponding Source conveyed, and Installation Information provided,
339
+
in accord with this section must be in a format that is publicly
340
+
documented (and with an implementation available to the public in
341
+
source code form), and must require no special password or key for
342
+
unpacking, reading or copying.
343
+
344
+
### 7. Additional Terms.
345
+
346
+
"Additional permissions" are terms that supplement the terms of this
347
+
License by making exceptions from one or more of its conditions.
348
+
Additional permissions that are applicable to the entire Program shall
349
+
be treated as though they were included in this License, to the extent
350
+
that they are valid under applicable law. If additional permissions
351
+
apply only to part of the Program, that part may be used separately
352
+
under those permissions, but the entire Program remains governed by
353
+
this License without regard to the additional permissions.
354
+
355
+
When you convey a copy of a covered work, you may at your option
356
+
remove any additional permissions from that copy, or from any part of
357
+
it. (Additional permissions may be written to require their own
358
+
removal in certain cases when you modify the work.) You may place
359
+
additional permissions on material, added by you to a covered work,
360
+
for which you have or can give appropriate copyright permission.
361
+
362
+
Notwithstanding any other provision of this License, for material you
363
+
add to a covered work, you may (if authorized by the copyright holders
364
+
of that material) supplement the terms of this License with terms:
365
+
366
+
- a) Disclaiming warranty or limiting liability differently from the
367
+
terms of sections 15 and 16 of this License; or
368
+
- b) Requiring preservation of specified reasonable legal notices or
369
+
author attributions in that material or in the Appropriate Legal
370
+
Notices displayed by works containing it; or
371
+
- c) Prohibiting misrepresentation of the origin of that material,
372
+
or requiring that modified versions of such material be marked in
373
+
reasonable ways as different from the original version; or
374
+
- d) Limiting the use for publicity purposes of names of licensors
375
+
or authors of the material; or
376
+
- e) Declining to grant rights under trademark law for use of some
377
+
trade names, trademarks, or service marks; or
378
+
- f) Requiring indemnification of licensors and authors of that
379
+
material by anyone who conveys the material (or modified versions
380
+
of it) with contractual assumptions of liability to the recipient,
381
+
for any liability that these contractual assumptions directly
382
+
impose on those licensors and authors.
383
+
384
+
All other non-permissive additional terms are considered "further
385
+
restrictions" within the meaning of section 10. If the Program as you
386
+
received it, or any part of it, contains a notice stating that it is
387
+
governed by this License along with a term that is a further
388
+
restriction, you may remove that term. If a license document contains
389
+
a further restriction but permits relicensing or conveying under this
390
+
License, you may add to a covered work material governed by the terms
391
+
of that license document, provided that the further restriction does
392
+
not survive such relicensing or conveying.
393
+
394
+
If you add terms to a covered work in accord with this section, you
395
+
must place, in the relevant source files, a statement of the
396
+
additional terms that apply to those files, or a notice indicating
397
+
where to find the applicable terms.
398
+
399
+
Additional terms, permissive or non-permissive, may be stated in the
400
+
form of a separately written license, or stated as exceptions; the
401
+
above requirements apply either way.
402
+
403
+
### 8. Termination.
404
+
405
+
You may not propagate or modify a covered work except as expressly
406
+
provided under this License. Any attempt otherwise to propagate or
407
+
modify it is void, and will automatically terminate your rights under
408
+
this License (including any patent licenses granted under the third
409
+
paragraph of section 11).
410
+
411
+
However, if you cease all violation of this License, then your license
412
+
from a particular copyright holder is reinstated (a) provisionally,
413
+
unless and until the copyright holder explicitly and finally
414
+
terminates your license, and (b) permanently, if the copyright holder
415
+
fails to notify you of the violation by some reasonable means prior to
416
+
60 days after the cessation.
417
+
418
+
Moreover, your license from a particular copyright holder is
419
+
reinstated permanently if the copyright holder notifies you of the
420
+
violation by some reasonable means, this is the first time you have
421
+
received notice of violation of this License (for any work) from that
422
+
copyright holder, and you cure the violation prior to 30 days after
423
+
your receipt of the notice.
424
+
425
+
Termination of your rights under this section does not terminate the
426
+
licenses of parties who have received copies or rights from you under
427
+
this License. If your rights have been terminated and not permanently
428
+
reinstated, you do not qualify to receive new licenses for the same
429
+
material under section 10.
430
+
431
+
### 9. Acceptance Not Required for Having Copies.
432
+
433
+
You are not required to accept this License in order to receive or run
434
+
a copy of the Program. Ancillary propagation of a covered work
435
+
occurring solely as a consequence of using peer-to-peer transmission
436
+
to receive a copy likewise does not require acceptance. However,
437
+
nothing other than this License grants you permission to propagate or
438
+
modify any covered work. These actions infringe copyright if you do
439
+
not accept this License. Therefore, by modifying or propagating a
440
+
covered work, you indicate your acceptance of this License to do so.
441
+
442
+
### 10. Automatic Licensing of Downstream Recipients.
443
+
444
+
Each time you convey a covered work, the recipient automatically
445
+
receives a license from the original licensors, to run, modify and
446
+
propagate that work, subject to this License. You are not responsible
447
+
for enforcing compliance by third parties with this License.
448
+
449
+
An "entity transaction" is a transaction transferring control of an
450
+
organization, or substantially all assets of one, or subdividing an
451
+
organization, or merging organizations. If propagation of a covered
452
+
work results from an entity transaction, each party to that
453
+
transaction who receives a copy of the work also receives whatever
454
+
licenses to the work the party's predecessor in interest had or could
455
+
give under the previous paragraph, plus a right to possession of the
456
+
Corresponding Source of the work from the predecessor in interest, if
457
+
the predecessor has it or can get it with reasonable efforts.
458
+
459
+
You may not impose any further restrictions on the exercise of the
460
+
rights granted or affirmed under this License. For example, you may
461
+
not impose a license fee, royalty, or other charge for exercise of
462
+
rights granted under this License, and you may not initiate litigation
463
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
464
+
any patent claim is infringed by making, using, selling, offering for
465
+
sale, or importing the Program or any portion of it.
466
+
467
+
### 11. Patents.
468
+
469
+
A "contributor" is a copyright holder who authorizes use under this
470
+
License of the Program or a work on which the Program is based. The
471
+
work thus licensed is called the contributor's "contributor version".
472
+
473
+
A contributor's "essential patent claims" are all patent claims owned
474
+
or controlled by the contributor, whether already acquired or
475
+
hereafter acquired, that would be infringed by some manner, permitted
476
+
by this License, of making, using, or selling its contributor version,
477
+
but do not include claims that would be infringed only as a
478
+
consequence of further modification of the contributor version. For
479
+
purposes of this definition, "control" includes the right to grant
480
+
patent sublicenses in a manner consistent with the requirements of
481
+
this License.
482
+
483
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
484
+
patent license under the contributor's essential patent claims, to
485
+
make, use, sell, offer for sale, import and otherwise run, modify and
486
+
propagate the contents of its contributor version.
487
+
488
+
In the following three paragraphs, a "patent license" is any express
489
+
agreement or commitment, however denominated, not to enforce a patent
490
+
(such as an express permission to practice a patent or covenant not to
491
+
sue for patent infringement). To "grant" such a patent license to a
492
+
party means to make such an agreement or commitment not to enforce a
493
+
patent against the party.
494
+
495
+
If you convey a covered work, knowingly relying on a patent license,
496
+
and the Corresponding Source of the work is not available for anyone
497
+
to copy, free of charge and under the terms of this License, through a
498
+
publicly available network server or other readily accessible means,
499
+
then you must either (1) cause the Corresponding Source to be so
500
+
available, or (2) arrange to deprive yourself of the benefit of the
501
+
patent license for this particular work, or (3) arrange, in a manner
502
+
consistent with the requirements of this License, to extend the patent
503
+
license to downstream recipients. "Knowingly relying" means you have
504
+
actual knowledge that, but for the patent license, your conveying the
505
+
covered work in a country, or your recipient's use of the covered work
506
+
in a country, would infringe one or more identifiable patents in that
507
+
country that you have reason to believe are valid.
508
+
509
+
If, pursuant to or in connection with a single transaction or
510
+
arrangement, you convey, or propagate by procuring conveyance of, a
511
+
covered work, and grant a patent license to some of the parties
512
+
receiving the covered work authorizing them to use, propagate, modify
513
+
or convey a specific copy of the covered work, then the patent license
514
+
you grant is automatically extended to all recipients of the covered
515
+
work and works based on it.
516
+
517
+
A patent license is "discriminatory" if it does not include within the
518
+
scope of its coverage, prohibits the exercise of, or is conditioned on
519
+
the non-exercise of one or more of the rights that are specifically
520
+
granted under this License. You may not convey a covered work if you
521
+
are a party to an arrangement with a third party that is in the
522
+
business of distributing software, under which you make payment to the
523
+
third party based on the extent of your activity of conveying the
524
+
work, and under which the third party grants, to any of the parties
525
+
who would receive the covered work from you, a discriminatory patent
526
+
license (a) in connection with copies of the covered work conveyed by
527
+
you (or copies made from those copies), or (b) primarily for and in
528
+
connection with specific products or compilations that contain the
529
+
covered work, unless you entered into that arrangement, or that patent
530
+
license was granted, prior to 28 March 2007.
531
+
532
+
Nothing in this License shall be construed as excluding or limiting
533
+
any implied license or other defenses to infringement that may
534
+
otherwise be available to you under applicable patent law.
535
+
536
+
### 12. No Surrender of Others' Freedom.
537
+
538
+
If conditions are imposed on you (whether by court order, agreement or
539
+
otherwise) that contradict the conditions of this License, they do not
540
+
excuse you from the conditions of this License. If you cannot convey a
541
+
covered work so as to satisfy simultaneously your obligations under
542
+
this License and any other pertinent obligations, then as a
543
+
consequence you may not convey it at all. For example, if you agree to
544
+
terms that obligate you to collect a royalty for further conveying
545
+
from those to whom you convey the Program, the only way you could
546
+
satisfy both those terms and this License would be to refrain entirely
547
+
from conveying the Program.
548
+
549
+
### 13. Use with the GNU Affero General Public License.
550
+
551
+
Notwithstanding any other provision of this License, you have
552
+
permission to link or combine any covered work with a work licensed
553
+
under version 3 of the GNU Affero General Public License into a single
554
+
combined work, and to convey the resulting work. The terms of this
555
+
License will continue to apply to the part which is the covered work,
556
+
but the special requirements of the GNU Affero General Public License,
557
+
section 13, concerning interaction through a network will apply to the
558
+
combination as such.
559
+
560
+
### 14. Revised Versions of this License.
561
+
562
+
The Free Software Foundation may publish revised and/or new versions
563
+
of the GNU General Public License from time to time. Such new versions
564
+
will be similar in spirit to the present version, but may differ in
565
+
detail to address new problems or concerns.
566
+
567
+
Each version is given a distinguishing version number. If the Program
568
+
specifies that a certain numbered version of the GNU General Public
569
+
License "or any later version" applies to it, you have the option of
570
+
following the terms and conditions either of that numbered version or
571
+
of any later version published by the Free Software Foundation. If the
572
+
Program does not specify a version number of the GNU General Public
573
+
License, you may choose any version ever published by the Free
574
+
Software Foundation.
575
+
576
+
If the Program specifies that a proxy can decide which future versions
577
+
of the GNU General Public License can be used, that proxy's public
578
+
statement of acceptance of a version permanently authorizes you to
579
+
choose that version for the Program.
580
+
581
+
Later license versions may give you additional or different
582
+
permissions. However, no additional obligations are imposed on any
583
+
author or copyright holder as a result of your choosing to follow a
584
+
later version.
585
+
586
+
### 15. Disclaimer of Warranty.
587
+
588
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
589
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
590
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
591
+
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
592
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
593
+
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
594
+
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
595
+
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
596
+
CORRECTION.
597
+
598
+
### 16. Limitation of Liability.
599
+
600
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
602
+
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
603
+
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
604
+
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
605
+
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
606
+
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
607
+
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
608
+
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
609
+
610
+
### 17. Interpretation of Sections 15 and 16.
611
+
612
+
If the disclaimer of warranty and limitation of liability provided
613
+
above cannot be given local legal effect according to their terms,
614
+
reviewing courts shall apply local law that most closely approximates
615
+
an absolute waiver of all civil liability in connection with the
616
+
Program, unless a warranty or assumption of liability accompanies a
617
+
copy of the Program in return for a fee.
618
+
619
+
END OF TERMS AND CONDITIONS
620
+
621
+
## How to Apply These Terms to Your New Programs
622
+
623
+
If you develop a new program, and you want it to be of the greatest
624
+
possible use to the public, the best way to achieve this is to make it
625
+
free software which everyone can redistribute and change under these
626
+
terms.
627
+
628
+
To do so, attach the following notices to the program. It is safest to
629
+
attach them to the start of each source file to most effectively state
630
+
the exclusion of warranty; and each file should have at least the
631
+
"copyright" line and a pointer to where the full notice is found.
632
+
633
+
<one line to give the program's name and a brief idea of what it does.>
634
+
Copyright (C) <year> <name of author>
635
+
636
+
This program is free software: you can redistribute it and/or modify
637
+
it under the terms of the GNU General Public License as published by
638
+
the Free Software Foundation, either version 3 of the License, or
639
+
(at your option) any later version.
640
+
641
+
This program is distributed in the hope that it will be useful,
642
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
643
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
644
+
GNU General Public License for more details.
645
+
646
+
You should have received a copy of the GNU General Public License
647
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
648
+
649
+
Also add information on how to contact you by electronic and paper
650
+
mail.
651
+
652
+
If the program does terminal interaction, make it output a short
653
+
notice like this when it starts in an interactive mode:
654
+
655
+
<program> Copyright (C) <year> <name of author>
656
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+
This is free software, and you are welcome to redistribute it
658
+
under certain conditions; type `show c' for details.
659
+
660
+
The hypothetical commands \`show w' and \`show c' should show the
661
+
appropriate parts of the General Public License. Of course, your
662
+
program's commands might be different; for a GUI interface, you would
663
+
use an "about box".
664
+
665
+
You should also get your employer (if you work as a programmer) or
666
+
school, if any, to sign a "copyright disclaimer" for the program, if
667
+
necessary. For more information on this, and how to apply and follow
668
+
the GNU GPL, see <https://www.gnu.org/licenses/>.
669
+
670
+
The GNU General Public License does not permit incorporating your
671
+
program into proprietary programs. If your program is a subroutine
672
+
library, you may consider it more useful to permit linking proprietary
673
+
applications with the library. If this is what you want to do, use the
674
+
GNU Lesser General Public License instead of this License. But first,
675
+
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.
+10
README.md
+10
README.md
+40
deny.toml
+40
deny.toml
···
1
+
[graph]
2
+
all-features = true
3
+
no-default-features = false
4
+
5
+
[output]
6
+
feature-depth = 1
7
+
8
+
[advisories]
9
+
ignore = []
10
+
11
+
[licenses]
12
+
confidence-threshold = 0.9
13
+
allow = [
14
+
"Apache-2.0",
15
+
"MIT",
16
+
"GPL-3.0",
17
+
"Unicode-3.0"
18
+
]
19
+
20
+
[licenses.private]
21
+
ignore = false
22
+
registries = []
23
+
24
+
[bans]
25
+
multiple-versions = "warn"
26
+
wildcards = "allow"
27
+
highlight = "all"
28
+
workspace-default-features = "allow"
29
+
external-default-features = "allow"
30
+
31
+
[sources]
32
+
unknown-registry = "warn"
33
+
unknown-git = "warn"
34
+
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
35
+
allow-git = []
36
+
37
+
[sources.allow-org]
38
+
github = []
39
+
gitlab = []
40
+
bitbucket = []
+33
files/cat.asm
+33
files/cat.asm
···
1
+
;
2
+
; cat.asm
3
+
;
4
+
; Read from stdin and echo to stdout.
5
+
;
6
+
main:
7
+
; set r2 to 0xffffffff
8
+
nand r2
9
+
10
+
; setup branches
11
+
adr r6, output
12
+
adr r5, loop
13
+
14
+
loop:
15
+
; read stdin, r1 will contain 0xffffffff if we've reached EOF.
16
+
in r1
17
+
18
+
; set r3 to 0 if r2 == r1
19
+
nand r3, r2, r1
20
+
21
+
; setup branch
22
+
adr r4, end
23
+
; overwrite r4 with $output iff r3 == 0.
24
+
mov r4, r6, r3
25
+
jmp [r0, r4]
26
+
27
+
output:
28
+
; write to stdout
29
+
out r1
30
+
jmp [r0, r5]
31
+
32
+
end:
33
+
halt
+24
files/hello-world.asm
+24
files/hello-world.asm
···
1
+
;
2
+
; hello-world.asm
3
+
;
4
+
; Prints "Hello, world!" to the stdout.
5
+
;
6
+
message:
7
+
.wstr "Hello, world!\n"
8
+
9
+
adr r1, message
10
+
adr r4, loop
11
+
mov r3, 1
12
+
loop:
13
+
ldr r2, [r0, r1]
14
+
adr r6, next
15
+
adr r7, end
16
+
mov r7, r6, r2
17
+
jmp [r0, r7]
18
+
next:
19
+
out r2
20
+
add r1, r3
21
+
jmp [r0, r4]
22
+
23
+
end:
24
+
halt
+3
sandmark.sh
+3
sandmark.sh
+141
src/asm/lexer.rs
+141
src/asm/lexer.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
use crate::Register;
16
+
use logos::{Lexer, Logos};
17
+
18
+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
19
+
pub struct Extras {
20
+
pub line: usize,
21
+
}
22
+
23
+
#[derive(Logos, Debug, PartialEq)]
24
+
#[logos(skip r"[ \t\f,]+", extras = Extras)]
25
+
pub enum Token<'source> {
26
+
#[token("\n", lex_newline)]
27
+
Newline,
28
+
29
+
#[regex("[a-zA-Z]+[a-zA-Z0-9_]*:", lex_label)]
30
+
Label(&'source str),
31
+
32
+
#[regex("[a-zA-Z_]+[a-zA-Z0-9_]*", |lexer| lexer.slice())]
33
+
Ident(&'source str),
34
+
35
+
#[regex(r#"\.([a-zA-Z0-9]+)"#, |lexer| &lexer.slice()[1..])]
36
+
Pragma(&'source str),
37
+
38
+
#[token("[")]
39
+
AddressOpen,
40
+
41
+
#[token("]")]
42
+
AddressClose,
43
+
44
+
#[regex("r[0-7]", lex_register, priority = 10)]
45
+
Register(Register),
46
+
47
+
#[token("#")]
48
+
Pound,
49
+
50
+
#[token("+")]
51
+
Plus,
52
+
53
+
#[token("-")]
54
+
Minus,
55
+
56
+
#[token(".")]
57
+
Here,
58
+
59
+
#[regex(r#"(0x[a-fA-F0-9]+)|([0-9]+)"#, lex_number)]
60
+
Number(u32),
61
+
62
+
#[token("\"", lex_string_literal)]
63
+
String(&'source str),
64
+
65
+
#[token(";", lex_comment)]
66
+
Comment(&'source str),
67
+
}
68
+
69
+
fn lex_newline<'source>(lexer: &mut Lexer<'source, Token<'source>>) {
70
+
lexer.extras.line += 1;
71
+
}
72
+
73
+
fn lex_label<'source>(lex: &mut Lexer<'source, Token<'source>>) -> &'source str {
74
+
let slice = lex.slice();
75
+
&slice[..slice.len() - 1]
76
+
}
77
+
78
+
fn lex_number<'source>(lex: &mut Lexer<'source, Token<'source>>) -> u32 {
79
+
let slice = &lex.slice();
80
+
if slice.starts_with("0x") {
81
+
u32::from_str_radix(slice.trim_start_matches("0x"), 16).unwrap()
82
+
} else {
83
+
slice.parse().unwrap()
84
+
}
85
+
}
86
+
87
+
fn lex_string_literal<'source>(lexer: &mut Lexer<'source, Token<'source>>) -> &'source str {
88
+
let remainder = lexer.remainder();
89
+
90
+
let mut in_escape = false;
91
+
let mut complete = false;
92
+
let mut final_index = 0;
93
+
for (index, character) in remainder.char_indices() {
94
+
if complete {
95
+
lexer.bump(index);
96
+
return &remainder[..final_index];
97
+
}
98
+
99
+
if character == '\\' {
100
+
in_escape = true;
101
+
continue;
102
+
}
103
+
104
+
if character == '"' && in_escape {
105
+
continue;
106
+
}
107
+
108
+
if character == '"' && !in_escape {
109
+
complete = true;
110
+
final_index = index;
111
+
continue;
112
+
}
113
+
114
+
in_escape = false;
115
+
}
116
+
117
+
lexer.bump(remainder.len());
118
+
remainder
119
+
}
120
+
121
+
fn lex_register<'source>(lex: &mut Lexer<'source, Token<'source>>) -> Register {
122
+
let slice = lex.slice();
123
+
let index = slice[1..]
124
+
.parse()
125
+
.expect("regex for register tokens should make the infallible");
126
+
127
+
Register::from_u8(index)
128
+
}
129
+
130
+
fn lex_comment<'source>(lex: &mut Lexer<'source, Token<'source>>) -> &'source str {
131
+
let remainder = lex.remainder();
132
+
for (position, c) in remainder.char_indices() {
133
+
if c == '\n' {
134
+
lex.bump(position);
135
+
return &remainder[..position];
136
+
}
137
+
}
138
+
139
+
lex.bump(remainder.len());
140
+
remainder
141
+
}
+753
src/asm/parse.rs
+753
src/asm/parse.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
use super::Token;
16
+
use crate::Register;
17
+
use logos::{Logos, Source};
18
+
use std::{borrow::Cow, collections::HashMap, iter::Peekable, ops::Range, str::CharIndices};
19
+
20
+
pub fn parse(_unit: impl std::fmt::Display, source: &str) -> Result<ParsedProgram, Error> {
21
+
Parser::new(source).parse()
22
+
}
23
+
24
+
#[derive(Debug)]
25
+
pub enum NodeType<'s> {
26
+
Pragma(Pragma<'s>),
27
+
Instruction(Instruction<'s>),
28
+
Comment(#[allow(unused)] &'s str),
29
+
}
30
+
31
+
impl NodeType<'_> {
32
+
pub fn size(&self) -> usize {
33
+
match self {
34
+
Self::Pragma(pragma) => match &pragma.payload {
35
+
PragmaType::U32 { .. } => 1,
36
+
PragmaType::WideString { value } => value.len() + 1,
37
+
},
38
+
// Instructions are always one platter.
39
+
Self::Instruction(_) => 1,
40
+
Self::Comment(_) => 0,
41
+
}
42
+
}
43
+
}
44
+
45
+
#[derive(Debug)]
46
+
pub struct Node<'s> {
47
+
pub labels: Vec<&'s str>,
48
+
pub entity: NodeType<'s>,
49
+
#[allow(unused)]
50
+
pub span: Range<usize>,
51
+
}
52
+
53
+
impl Node<'_> {
54
+
/// Compute encoded size of the node in platters.
55
+
#[inline]
56
+
pub fn size(&self) -> usize {
57
+
self.entity.size()
58
+
}
59
+
}
60
+
61
+
#[derive(Debug)]
62
+
pub struct ParsedProgram<'s> {
63
+
#[allow(unused)]
64
+
pub source: &'s str,
65
+
nodes: Vec<Node<'s>>,
66
+
}
67
+
68
+
impl<'s> ParsedProgram<'s> {
69
+
pub fn nodes(&self) -> &[Node<'s>] {
70
+
&self.nodes
71
+
}
72
+
}
73
+
74
+
#[derive(Debug, Default)]
75
+
pub struct Parser<'s> {
76
+
source: &'s str,
77
+
labels: HashMap<&'s str, Range<usize>>,
78
+
active_labels: Vec<&'s str>,
79
+
}
80
+
81
+
impl<'s> Parser<'s> {
82
+
fn new(source: &'s str) -> Self {
83
+
Self {
84
+
source,
85
+
..Default::default()
86
+
}
87
+
}
88
+
89
+
fn parse(mut self) -> Result<ParsedProgram<'s>, Error> {
90
+
let mut lexer = Token::lexer(self.source);
91
+
let mut spanned = vec![];
92
+
while let Some(res) = lexer.next() {
93
+
match res {
94
+
Ok(token) => {
95
+
spanned.push((token, lexer.span()));
96
+
}
97
+
Err(error) => Err(Error::new(format!("lex: {error:?}"), &lexer.span()))?,
98
+
}
99
+
}
100
+
101
+
let mut nodes = vec![];
102
+
let mut tokens = spanned.into_iter().peekable();
103
+
while let Some((token, span)) = tokens.peek() {
104
+
let node = match token {
105
+
Token::Label(_) => {
106
+
self.consume_label(&mut tokens)?;
107
+
continue;
108
+
}
109
+
Token::Pragma(_) => self.consume_pragma(&mut tokens)?,
110
+
Token::Ident(_) => self.consume_instruction(&mut tokens)?,
111
+
Token::Comment(comment) => {
112
+
let node = Node {
113
+
labels: vec![],
114
+
entity: NodeType::Comment(comment),
115
+
span: span.clone(),
116
+
};
117
+
tokens.next();
118
+
node
119
+
}
120
+
Token::Newline => {
121
+
tokens.next();
122
+
continue;
123
+
}
124
+
_ => Err(Error::new(format!("unexpected token {token:?}"), span))?,
125
+
};
126
+
127
+
nodes.push(node);
128
+
}
129
+
130
+
Ok(ParsedProgram {
131
+
source: self.source,
132
+
nodes,
133
+
})
134
+
}
135
+
136
+
/// Consumes a label from the token stream.
137
+
fn consume_label<I>(&mut self, tokens: &mut I) -> Result<(), Error>
138
+
where
139
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
140
+
{
141
+
let Some((Token::Label(label_ident), span)) = tokens.next() else {
142
+
unreachable!("consume_label called on non-label token");
143
+
};
144
+
145
+
// Add the label to the set of observed labels.
146
+
let label_span = self
147
+
.labels
148
+
.entry(label_ident)
149
+
.or_insert_with(|| span.clone());
150
+
151
+
// If the span of the current token is not equal to
152
+
// `label_span`, then we have already seen label with the
153
+
// same identifier.
154
+
if label_span != &span {
155
+
return Err(Error::new(
156
+
format!("duplicate label '{label_ident}', original label span: {label_span:?}"),
157
+
&span,
158
+
));
159
+
}
160
+
161
+
self.active_labels.push(label_ident);
162
+
Ok(())
163
+
}
164
+
165
+
fn consume_pragma<I>(&mut self, tokens: &mut Peekable<I>) -> Result<Node<'s>, Error>
166
+
where
167
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
168
+
{
169
+
assert!(
170
+
matches!(tokens.peek(), Some((Token::Pragma(_), _))),
171
+
"consume_pragma called on non-pragma token"
172
+
);
173
+
174
+
let labels = std::mem::take(&mut self.active_labels);
175
+
let (pragma, span) = Pragma::consume(tokens)?;
176
+
177
+
Ok(Node {
178
+
labels,
179
+
entity: NodeType::Pragma(pragma),
180
+
span,
181
+
})
182
+
}
183
+
184
+
fn consume_instruction<I>(&mut self, tokens: &mut Peekable<I>) -> Result<Node<'s>, Error>
185
+
where
186
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
187
+
{
188
+
assert!(
189
+
matches!(tokens.peek(), Some((Token::Ident(_), _))),
190
+
"consume_instruction called on non-ident token"
191
+
);
192
+
193
+
let labels = std::mem::take(&mut self.active_labels);
194
+
let (instr, span) = Instruction::consume(tokens)?;
195
+
Ok(Node {
196
+
labels,
197
+
entity: NodeType::Instruction(instr),
198
+
span,
199
+
})
200
+
}
201
+
}
202
+
203
+
/// An error encountered during parsing.
204
+
#[derive(Debug)]
205
+
#[allow(unused)]
206
+
pub struct Error(pub String, pub Range<usize>);
207
+
208
+
impl Error {
209
+
fn new(message: impl ToString, span: &Range<usize>) -> Self {
210
+
Self(message.to_string(), span.clone())
211
+
}
212
+
213
+
fn eof() -> Self {
214
+
Self("unexpected eof".into(), 0..0)
215
+
}
216
+
}
217
+
218
+
impl std::fmt::Display for Error {
219
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220
+
write!(f, "{self:?}")
221
+
}
222
+
}
223
+
224
+
impl std::error::Error for Error {}
225
+
226
+
#[derive(Debug, Default)]
227
+
pub struct Location {
228
+
pub block: Register,
229
+
pub offset: Register,
230
+
}
231
+
232
+
impl Location {
233
+
pub fn consume<'s, I>(tokens: &mut Peekable<I>) -> Result<(Self, Range<usize>), Error>
234
+
where
235
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
236
+
{
237
+
// Require a '[' token.
238
+
let start_span = match tokens.next() {
239
+
Some((Token::AddressOpen, span)) => span,
240
+
Some((_, span)) => Err(Error::new("expected an address opening bracket", &span))?,
241
+
_ => Err(Error::eof())?,
242
+
};
243
+
244
+
let (block, _) = consume_register(tokens)?;
245
+
let (offset, _) = consume_register(tokens)?;
246
+
247
+
// Require a ']' token.
248
+
let end_span = match tokens.next() {
249
+
Some((Token::AddressClose, span)) => span,
250
+
Some((_, span)) => Err(Error::new("expected an address closing bracket", &span))?,
251
+
_ => Err(Error::eof())?,
252
+
};
253
+
254
+
Ok((Self { block, offset }, merge_spans(&start_span, &end_span)))
255
+
}
256
+
}
257
+
258
+
#[derive(Debug)]
259
+
pub struct Expr<'s> {
260
+
pub label: &'s str,
261
+
}
262
+
263
+
#[derive(Debug)]
264
+
pub enum PragmaType<'s> {
265
+
U32 { value: u32 },
266
+
WideString { value: Cow<'s, str> },
267
+
}
268
+
269
+
#[derive(Debug)]
270
+
pub struct Pragma<'s> {
271
+
#[allow(unused)]
272
+
relocatable: bool,
273
+
pub payload: PragmaType<'s>,
274
+
}
275
+
276
+
impl<'s> Pragma<'s> {
277
+
pub fn consume<I>(tokens: &mut Peekable<I>) -> Result<(Self, Range<usize>), Error>
278
+
where
279
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
280
+
{
281
+
let relocatable = true;
282
+
let token = tokens.next().ok_or(Error::eof())?;
283
+
match token {
284
+
(Token::Pragma("u32"), start_span) => {
285
+
let (value, end_span) = consume_number(tokens)?;
286
+
Ok((
287
+
Self {
288
+
relocatable,
289
+
payload: PragmaType::U32 { value },
290
+
},
291
+
merge_spans(&start_span, &end_span),
292
+
))
293
+
}
294
+
(Token::Pragma("wstr"), start_span) => {
295
+
let (value, end_span) = consume_string(tokens)?;
296
+
Ok((
297
+
Self {
298
+
relocatable,
299
+
payload: PragmaType::WideString { value },
300
+
},
301
+
merge_spans(&start_span, &end_span),
302
+
))
303
+
}
304
+
(Token::Pragma(command), span) => Err(Error::new(
305
+
format!("unknown pragma command {command}"),
306
+
&span,
307
+
))?,
308
+
(_, span) => Err(Error::new("unexpected token", &span))?,
309
+
}
310
+
}
311
+
}
312
+
313
+
#[derive(Debug)]
314
+
pub enum Instruction<'s> {
315
+
/// Operation #0.
316
+
ConditionalMove {
317
+
destination: Register,
318
+
source: Register,
319
+
condition: Register,
320
+
},
321
+
/// Operation #13.
322
+
Address {
323
+
destination: Register,
324
+
reference: Expr<'s>,
325
+
},
326
+
/// Operation #13.
327
+
LiteralMove {
328
+
destination: Register,
329
+
literal: u32,
330
+
},
331
+
Load {
332
+
destination: Register,
333
+
address: Location,
334
+
},
335
+
Store {
336
+
source: Register,
337
+
address: Location,
338
+
},
339
+
Add {
340
+
destination: Register,
341
+
a: Register,
342
+
b: Register,
343
+
},
344
+
AddAssign {
345
+
destination: Register,
346
+
a: Register,
347
+
},
348
+
AddSelf {
349
+
destination: Register,
350
+
},
351
+
Mul {
352
+
destination: Register,
353
+
a: Register,
354
+
b: Register,
355
+
},
356
+
MulAssign {
357
+
destination: Register,
358
+
a: Register,
359
+
},
360
+
MulSelf {
361
+
destination: Register,
362
+
},
363
+
Div {
364
+
destination: Register,
365
+
a: Register,
366
+
b: Register,
367
+
},
368
+
DivAssign {
369
+
destination: Register,
370
+
a: Register,
371
+
},
372
+
DivSelf {
373
+
destination: Register,
374
+
},
375
+
Nand {
376
+
destination: Register,
377
+
a: Register,
378
+
b: Register,
379
+
},
380
+
NandAssign {
381
+
destination: Register,
382
+
a: Register,
383
+
},
384
+
NandSelf {
385
+
destination: Register,
386
+
},
387
+
Halt,
388
+
Alloc {
389
+
destination: Register,
390
+
length: Register,
391
+
},
392
+
Free {
393
+
block: Register,
394
+
},
395
+
Out {
396
+
source: Register,
397
+
},
398
+
In {
399
+
destination: Register,
400
+
},
401
+
Jmp {
402
+
location: Location,
403
+
},
404
+
}
405
+
406
+
impl<'s> Instruction<'s> {
407
+
pub fn consume<I>(tokens: &mut Peekable<I>) -> Result<(Self, Range<usize>), Error>
408
+
where
409
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
410
+
{
411
+
let ident = tokens.next().unwrap();
412
+
match ident {
413
+
(Token::Ident("halt"), span) => Ok((Self::Halt, span)),
414
+
(Token::Ident("adr"), start_span) => {
415
+
let (destination, _) = consume_register(tokens)?;
416
+
let (identifier, end_span) = consume_ident(tokens)?;
417
+
Ok((
418
+
Self::Address {
419
+
destination,
420
+
reference: Expr { label: identifier },
421
+
},
422
+
merge_spans(&start_span, &end_span),
423
+
))
424
+
}
425
+
(Token::Ident("mov"), start_span) => {
426
+
let (destination, _) = consume_register(tokens)?;
427
+
if peek_register(tokens)?.is_some() {
428
+
let (source, _) = consume_register(tokens)?;
429
+
let (condition, end_span) = consume_register(tokens)?;
430
+
Ok((
431
+
Self::ConditionalMove {
432
+
destination,
433
+
source,
434
+
condition,
435
+
},
436
+
merge_spans(&start_span, &end_span),
437
+
))
438
+
} else {
439
+
let (literal, end_span) = consume_number(tokens)?;
440
+
Ok((
441
+
Self::LiteralMove {
442
+
destination,
443
+
literal,
444
+
},
445
+
merge_spans(&start_span, &end_span),
446
+
))
447
+
}
448
+
}
449
+
(Token::Ident("ldr"), start_span) => {
450
+
let (destination, _) = consume_register(tokens)?;
451
+
let (address, end_span) = Location::consume(tokens)?;
452
+
Ok((
453
+
Self::Load {
454
+
destination,
455
+
address,
456
+
},
457
+
merge_spans(&start_span, &end_span),
458
+
))
459
+
}
460
+
(Token::Ident("str"), start_span) => {
461
+
let (source, _) = consume_register(tokens)?;
462
+
let (address, end_span) = Location::consume(tokens)?;
463
+
Ok((
464
+
Self::Store { source, address },
465
+
merge_spans(&start_span, &end_span),
466
+
))
467
+
}
468
+
(Token::Ident("out"), start_span) => {
469
+
let (source, end_span) = consume_register(tokens)?;
470
+
Ok((Self::Out { source }, merge_spans(&start_span, &end_span)))
471
+
}
472
+
(Token::Ident("in"), start_span) => {
473
+
let (destination, end_span) = consume_register(tokens)?;
474
+
Ok((
475
+
Self::In { destination },
476
+
merge_spans(&start_span, &end_span),
477
+
))
478
+
}
479
+
(Token::Ident("alloc"), start_span) => {
480
+
let (destination, _) = consume_register(tokens)?;
481
+
let (length, end_span) = consume_register(tokens)?;
482
+
Ok((
483
+
Self::Alloc {
484
+
length,
485
+
destination,
486
+
},
487
+
merge_spans(&start_span, &end_span),
488
+
))
489
+
}
490
+
(Token::Ident("free"), start_span) => {
491
+
let (block, end_span) = consume_register(tokens)?;
492
+
Ok((Self::Free { block }, merge_spans(&start_span, &end_span)))
493
+
}
494
+
(Token::Ident("jmp"), start_span) => {
495
+
let (location, end_span) = Location::consume(tokens)?;
496
+
Ok((Self::Jmp { location }, merge_spans(&start_span, &end_span)))
497
+
}
498
+
(Token::Ident("add"), start_span) => {
499
+
let (destination, mid_span) = consume_register(tokens)?;
500
+
let a = peek_register(tokens)?.and_then(|_| consume_register(tokens).ok());
501
+
let b = peek_register(tokens)?.and_then(|_| consume_register(tokens).ok());
502
+
match (a, b) {
503
+
(Some((a, _)), Some((b, end_span))) => Ok((
504
+
Self::Add { destination, a, b },
505
+
merge_spans(&start_span, &end_span),
506
+
)),
507
+
(Some((a, end_span)), None) => Ok((
508
+
Self::AddAssign { destination, a },
509
+
merge_spans(&start_span, &end_span),
510
+
)),
511
+
(None, None) => Ok((
512
+
Self::AddSelf { destination },
513
+
merge_spans(&start_span, &mid_span),
514
+
)),
515
+
_ => unreachable!(),
516
+
}
517
+
}
518
+
(Token::Ident("mul"), start_span) => {
519
+
let (destination, mid_span) = consume_register(tokens)?;
520
+
let a = peek_register(tokens)?.and_then(|_| consume_register(tokens).ok());
521
+
let b = peek_register(tokens)?.and_then(|_| consume_register(tokens).ok());
522
+
match (a, b) {
523
+
(Some((a, _)), Some((b, end_span))) => Ok((
524
+
Self::Mul { destination, a, b },
525
+
merge_spans(&start_span, &end_span),
526
+
)),
527
+
(Some((a, end_span)), None) => Ok((
528
+
Self::MulAssign { destination, a },
529
+
merge_spans(&start_span, &end_span),
530
+
)),
531
+
(None, None) => Ok((
532
+
Self::MulSelf { destination },
533
+
merge_spans(&start_span, &mid_span),
534
+
)),
535
+
_ => unreachable!(),
536
+
}
537
+
}
538
+
(Token::Ident("div"), start_span) => {
539
+
let (destination, mid_span) = consume_register(tokens)?;
540
+
let a = peek_register(tokens)?.and_then(|_| consume_register(tokens).ok());
541
+
let b = peek_register(tokens)?.and_then(|_| consume_register(tokens).ok());
542
+
match (a, b) {
543
+
(Some((a, _)), Some((b, end_span))) => Ok((
544
+
Self::Div { destination, a, b },
545
+
merge_spans(&start_span, &end_span),
546
+
)),
547
+
(Some((a, end_span)), None) => Ok((
548
+
Self::DivAssign { destination, a },
549
+
merge_spans(&start_span, &end_span),
550
+
)),
551
+
(None, None) => Ok((
552
+
Self::DivSelf { destination },
553
+
merge_spans(&start_span, &mid_span),
554
+
)),
555
+
_ => unreachable!(),
556
+
}
557
+
}
558
+
(Token::Ident("nand"), start_span) => {
559
+
let (destination, mid_span) = consume_register(tokens)?;
560
+
let a = peek_register(tokens)?.and_then(|_| consume_register(tokens).ok());
561
+
let b = peek_register(tokens)?.and_then(|_| consume_register(tokens).ok());
562
+
match (a, b) {
563
+
(Some((a, _)), Some((b, end_span))) => Ok((
564
+
Self::Nand { destination, a, b },
565
+
merge_spans(&start_span, &end_span),
566
+
)),
567
+
(Some((a, end_span)), None) => Ok((
568
+
Self::NandAssign { destination, a },
569
+
merge_spans(&start_span, &end_span),
570
+
)),
571
+
(None, None) => Ok((
572
+
Self::NandSelf { destination },
573
+
merge_spans(&start_span, &mid_span),
574
+
)),
575
+
_ => unreachable!(),
576
+
}
577
+
}
578
+
(_, span) => Err(Error::new("unrecognised instruction", &span))?,
579
+
}
580
+
}
581
+
}
582
+
583
+
impl std::fmt::Display for Instruction<'_> {
584
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
585
+
match self {
586
+
Self::ConditionalMove {
587
+
destination,
588
+
source,
589
+
condition,
590
+
} => write!(f, "mov {destination}, {source}, {condition}"),
591
+
Self::Load {
592
+
destination,
593
+
address,
594
+
} => write!(
595
+
f,
596
+
"ldr {destination}, [{}, {}]",
597
+
address.block, address.offset
598
+
),
599
+
Self::Store { source, address } => {
600
+
write!(f, "str {source}, [{}, {}]", address.block, address.offset)
601
+
}
602
+
Self::Add { destination, a, b } => write!(f, "add {destination}, {a}, {b}"),
603
+
Self::AddAssign { destination, a } => write!(f, "add {destination}, {a}"),
604
+
Self::AddSelf { destination } => write!(f, "add {destination}"),
605
+
Self::Mul { destination, a, b } => write!(f, "mul {destination}, {a}, {b}"),
606
+
Self::MulAssign { destination, a } => write!(f, "mul {destination}, {a}"),
607
+
Self::MulSelf { destination } => write!(f, "mul {destination}"),
608
+
Self::Div { destination, a, b } => write!(f, "div {destination}, {a}, {b}"),
609
+
Self::DivAssign { destination, a } => write!(f, "div {destination}, {a}"),
610
+
Self::DivSelf { destination } => write!(f, "div {destination}"),
611
+
Self::Nand { destination, a, b } => write!(f, "nand {destination}, {a}, {b}"),
612
+
Self::NandAssign { destination, a } => write!(f, "nand {destination}, {a}"),
613
+
Self::NandSelf { destination } => write!(f, "nand {destination}"),
614
+
Self::Halt => write!(f, "halt"),
615
+
Self::Out { source } => write!(f, "out {source}"),
616
+
Self::In { destination } => write!(f, "in {destination}"),
617
+
Self::Alloc {
618
+
length,
619
+
destination,
620
+
} => write!(f, "alloc {destination}, {length}"),
621
+
Self::Free { block } => {
622
+
write!(f, "free {block}")
623
+
}
624
+
Self::Jmp { location } => write!(f, "jmp [{}, {}]", location.block, location.offset),
625
+
Self::LiteralMove {
626
+
destination,
627
+
literal,
628
+
} => write!(f, "mov {destination}, {literal}"),
629
+
Self::Address {
630
+
destination,
631
+
reference,
632
+
} => write!(f, "adr {destination}, {}", reference.label),
633
+
}
634
+
}
635
+
}
636
+
637
+
/// Peeks at the next token and returns it iff it is a Register.
638
+
fn peek_register<'s, I>(tokens: &mut Peekable<I>) -> Result<Option<Register>, Error>
639
+
where
640
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
641
+
{
642
+
match tokens.peek() {
643
+
Some((Token::Register(r), _)) => Ok(Some(*r)),
644
+
Some(_) => Ok(None),
645
+
None => Err(Error::new("unexpected eof", &(0..0))),
646
+
}
647
+
}
648
+
649
+
fn consume_register<'s, I>(tokens: &mut I) -> Result<(Register, Range<usize>), Error>
650
+
where
651
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
652
+
{
653
+
match tokens.next() {
654
+
Some((Token::Register(r), span)) => Ok((r, span)),
655
+
Some((token, span)) => Err(Error::new(
656
+
format!("expected a register, found: {token:?}"),
657
+
&span,
658
+
)),
659
+
None => Err(Error::eof()),
660
+
}
661
+
}
662
+
663
+
fn consume_ident<'s, I>(tokens: &mut I) -> Result<(&'s str, Range<usize>), Error>
664
+
where
665
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
666
+
{
667
+
match tokens.next() {
668
+
Some((Token::Ident(ident), span)) => Ok((ident, span)),
669
+
Some((token, span)) => Err(Error::new(
670
+
format!("expected an identifier, found: {token:?}"),
671
+
&span,
672
+
)),
673
+
None => Err(Error::eof()),
674
+
}
675
+
}
676
+
677
+
fn consume_number<'s, I>(tokens: &mut I) -> Result<(u32, Range<usize>), Error>
678
+
where
679
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
680
+
{
681
+
match tokens.next() {
682
+
Some((Token::Number(value), span)) => Ok((value, span)),
683
+
Some((token, span)) => Err(Error::new(
684
+
format!("expected a number literal, found: {token:?}"),
685
+
&span,
686
+
)),
687
+
None => Err(Error::eof()),
688
+
}
689
+
}
690
+
691
+
fn consume_string<'s, I>(tokens: &mut I) -> Result<(Cow<'s, str>, Range<usize>), Error>
692
+
where
693
+
I: Iterator<Item = (Token<'s>, Range<usize>)>,
694
+
{
695
+
match tokens.next() {
696
+
Some((Token::String(value), span)) => {
697
+
let unescaped = unescape_str(value).map_err(|_| Error::eof())?;
698
+
Ok((unescaped, span))
699
+
}
700
+
Some((token, span)) => Err(Error::new(
701
+
format!("expected a number literal, found: {token:?}"),
702
+
&span,
703
+
)),
704
+
None => Err(Error::eof()),
705
+
}
706
+
}
707
+
708
+
fn merge_spans(start: &Range<usize>, end: &Range<usize>) -> Range<usize> {
709
+
start.start..end.end
710
+
}
711
+
712
+
#[derive(Debug)]
713
+
#[allow(unused)]
714
+
pub struct InvalidCharacterEscape(pub char, pub usize);
715
+
716
+
pub fn unescape_str(s: &str) -> Result<Cow<str>, InvalidCharacterEscape> {
717
+
fn escape_inner(c: &str, i: &mut CharIndices<'_>) -> Result<String, InvalidCharacterEscape> {
718
+
let mut buffer = c.to_owned();
719
+
let mut in_escape = true;
720
+
721
+
for (index, c) in i {
722
+
match (in_escape, c) {
723
+
(false, '\\') => {
724
+
in_escape = true;
725
+
continue;
726
+
}
727
+
(false, c) => buffer.push(c),
728
+
(true, '\\') => buffer.push('\\'),
729
+
(true, 'n') => buffer.push('\n'),
730
+
(true, '0') => buffer.push('\0'),
731
+
(true, '"') => buffer.push('"'),
732
+
(true, '\'') => buffer.push('\''),
733
+
(true, 'r') => buffer.push('\r'),
734
+
(true, 't') => buffer.push('\t'),
735
+
(true, c) => Err(InvalidCharacterEscape(c, index))?,
736
+
}
737
+
738
+
in_escape = false;
739
+
}
740
+
741
+
Ok(buffer)
742
+
}
743
+
744
+
let mut char_indicies = s.char_indices();
745
+
for (index, c) in &mut char_indicies {
746
+
let scanned = &s[..index];
747
+
if c == '\\' {
748
+
return Ok(Cow::Owned(escape_inner(scanned, &mut char_indicies)?));
749
+
}
750
+
}
751
+
752
+
Ok(Cow::Borrowed(s))
753
+
}
+349
src/asm.rs
+349
src/asm.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
mod lexer;
16
+
mod parse;
17
+
18
+
use crate::Register;
19
+
use lexer::Token;
20
+
use parse::{Instruction, Node, NodeType, PragmaType};
21
+
use std::collections::HashMap;
22
+
23
+
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
24
+
enum Section {
25
+
Text,
26
+
Data,
27
+
}
28
+
29
+
pub fn assemble<'s>(source: &'s str) -> Vec<u32> {
30
+
let parsed = parse::parse("", source).unwrap();
31
+
32
+
let mut sections: HashMap<Section, Vec<&Node<'s>>> = HashMap::new();
33
+
let mut offsets: HashMap<Section, usize> = HashMap::new();
34
+
let mut label_locations: HashMap<&'s str, (Section, usize)> = HashMap::new();
35
+
for node in parsed.nodes().iter() {
36
+
match node.entity {
37
+
NodeType::Pragma(_) => {
38
+
let loc = *offsets
39
+
.entry(Section::Data)
40
+
.and_modify(|loc| *loc += node.size())
41
+
.or_default();
42
+
43
+
sections
44
+
.entry(Section::Data)
45
+
.and_modify(|section| section.push(node))
46
+
.or_insert(vec![node]);
47
+
48
+
for label in &node.labels {
49
+
label_locations.insert(label, (Section::Data, loc));
50
+
}
51
+
}
52
+
NodeType::Instruction(_) => {
53
+
let loc = *offsets
54
+
.entry(Section::Text)
55
+
.and_modify(|loc| *loc += node.size())
56
+
.or_default();
57
+
58
+
sections
59
+
.entry(Section::Text)
60
+
.and_modify(|section| section.push(node))
61
+
.or_insert(vec![node]);
62
+
63
+
for label in &node.labels {
64
+
label_locations.insert(label, (Section::Text, loc));
65
+
}
66
+
}
67
+
_ => {}
68
+
}
69
+
}
70
+
71
+
let text = sections.remove(&Section::Text).unwrap();
72
+
let data_offset = text.len();
73
+
74
+
let mut program = vec![];
75
+
for node in text.into_iter() {
76
+
let NodeType::Instruction(instruction) = &node.entity else {
77
+
panic!("invalid node in .text section");
78
+
};
79
+
80
+
let encoded = match instruction {
81
+
Instruction::ConditionalMove {
82
+
destination,
83
+
source,
84
+
condition,
85
+
} => encode_standard(0x00, destination, source, condition),
86
+
Instruction::Load {
87
+
destination,
88
+
address,
89
+
} => {
90
+
let parse::Location { block, offset } = address;
91
+
encode_standard(0x01, destination, block, offset)
92
+
}
93
+
Instruction::Store { source, address } => {
94
+
let parse::Location { block, offset } = address;
95
+
encode_standard(0x02, block, offset, source)
96
+
}
97
+
Instruction::Add { destination, a, b } => encode_standard(0x03, destination, a, b),
98
+
Instruction::AddAssign { destination, a } => {
99
+
encode_standard(0x03, destination, destination, a)
100
+
}
101
+
Instruction::AddSelf { destination } => {
102
+
encode_standard(0x03, destination, destination, destination)
103
+
}
104
+
Instruction::Mul { destination, a, b } => encode_standard(0x04, destination, a, b),
105
+
Instruction::MulAssign { destination, a } => {
106
+
encode_standard(0x04, destination, destination, a)
107
+
}
108
+
Instruction::MulSelf { destination } => {
109
+
encode_standard(0x04, destination, destination, destination)
110
+
}
111
+
Instruction::Div { destination, a, b } => encode_standard(0x05, destination, a, b),
112
+
Instruction::DivAssign { destination, a } => {
113
+
encode_standard(0x05, destination, destination, a)
114
+
}
115
+
Instruction::DivSelf { destination } => {
116
+
encode_standard(0x05, destination, destination, destination)
117
+
}
118
+
Instruction::Nand { destination, a, b } => encode_standard(0x06, destination, a, b),
119
+
Instruction::NandAssign { destination, a } => {
120
+
encode_standard(0x06, destination, destination, a)
121
+
}
122
+
Instruction::NandSelf { destination } => {
123
+
encode_standard(0x06, destination, destination, destination)
124
+
}
125
+
Instruction::Halt => encode_standard(
126
+
0x07,
127
+
&Default::default(),
128
+
&Default::default(),
129
+
&Default::default(),
130
+
),
131
+
Instruction::Alloc {
132
+
destination,
133
+
length,
134
+
} => encode_standard(0x08, &Register::default(), destination, length),
135
+
Instruction::Free { block } => {
136
+
encode_standard(0x09, &Register::default(), &Register::default(), block)
137
+
}
138
+
Instruction::Out { source } => {
139
+
encode_standard(0x0a, &Default::default(), &Default::default(), source)
140
+
}
141
+
Instruction::In { destination } => {
142
+
encode_standard(0x0b, &Default::default(), &Default::default(), destination)
143
+
}
144
+
Instruction::Jmp { location } => {
145
+
let parse::Location { block, offset } = location;
146
+
encode_standard(0x0c, &Register::default(), block, offset)
147
+
}
148
+
Instruction::Address {
149
+
destination,
150
+
reference,
151
+
} => {
152
+
// lookup reference
153
+
let Some((section, offset)) = label_locations.get(reference.label) else {
154
+
panic!("failed to resolve {}", reference.label);
155
+
};
156
+
157
+
let value = match section {
158
+
Section::Text => *offset,
159
+
Section::Data => data_offset + *offset,
160
+
};
161
+
162
+
0xd0000000 | destination.encode_a_ortho() | encode_literal(value as u32)
163
+
}
164
+
Instruction::LiteralMove {
165
+
destination,
166
+
literal,
167
+
} => 0xd0000000 | destination.encode_a_ortho() | encode_literal(*literal),
168
+
};
169
+
170
+
program.push(encoded);
171
+
}
172
+
173
+
if let Some(data) = sections.remove(&Section::Data) {
174
+
for node in data.into_iter() {
175
+
let NodeType::Pragma(pragma) = &node.entity else {
176
+
panic!("invalid node in .data section. {node:?}");
177
+
};
178
+
179
+
let encoded = match &pragma.payload {
180
+
PragmaType::WideString { value } => {
181
+
for byte in value.as_bytes() {
182
+
program.push(*byte as u32);
183
+
}
184
+
Some(0) // terminating byte.
185
+
}
186
+
PragmaType::U32 { value } => Some(*value),
187
+
};
188
+
189
+
if let Some(encoded) = encoded {
190
+
program.push(encoded);
191
+
}
192
+
}
193
+
}
194
+
195
+
program
196
+
}
197
+
198
+
fn encode_literal(value: u32) -> u32 {
199
+
const LITERAL_MAX: u32 = 0x1ffffff;
200
+
assert!(value <= LITERAL_MAX, "literal value exceeds available bits. value: {value} (0x{value:x}), max: {LITERAL_MAX} (0x{LITERAL_MAX:x})");
201
+
value
202
+
}
203
+
204
+
fn encode_standard(op: u32, a: &Register, b: &Register, c: &Register) -> u32 {
205
+
(op << 28) | a.encode_a() | b.encode_b() | c.encode_c()
206
+
}
207
+
208
+
#[cfg(test)]
209
+
mod tests {
210
+
use super::*;
211
+
use crate::{Operation, Register::*};
212
+
213
+
#[test]
214
+
fn wide_str() {
215
+
// Embed a wide string and get a reference to it.
216
+
let program = assemble(
217
+
r#"
218
+
adr r0, msg
219
+
msg: .wstr "Hello"
220
+
"#,
221
+
);
222
+
223
+
let ops = crate::ops::decode(&program);
224
+
assert_eq!(ops[0], Operation::Orthography { a: R0, value: 1 });
225
+
226
+
let mut platters = program.into_iter().skip(1);
227
+
assert_eq!(platters.next(), Some('H' as u32));
228
+
assert_eq!(platters.next(), Some('e' as u32));
229
+
assert_eq!(platters.next(), Some('l' as u32));
230
+
assert_eq!(platters.next(), Some('l' as u32));
231
+
assert_eq!(platters.next(), Some('o' as u32));
232
+
assert_eq!(platters.next(), Some(0));
233
+
assert_eq!(platters.next(), None);
234
+
}
235
+
236
+
#[test]
237
+
fn addresses() {
238
+
let program = assemble(
239
+
r#"
240
+
halt
241
+
start:
242
+
ldr r2, [r0, r1]
243
+
str r2, [r0, r1]
244
+
adr r3, start
245
+
halt
246
+
"#,
247
+
);
248
+
249
+
let mut ops = crate::ops::decode(&program).into_iter();
250
+
251
+
assert_eq!(ops.next(), Some(Operation::Halt));
252
+
assert_eq!(
253
+
ops.next(),
254
+
Some(Operation::ArrayIndex {
255
+
a: R2,
256
+
b: R0,
257
+
c: R1
258
+
})
259
+
);
260
+
assert_eq!(
261
+
ops.next(),
262
+
Some(Operation::ArrayAmendment {
263
+
a: R0,
264
+
b: R1,
265
+
c: R2
266
+
})
267
+
);
268
+
assert_eq!(ops.next(), Some(Operation::Orthography { a: R3, value: 1 }));
269
+
assert_eq!(ops.next(), Some(Operation::Halt));
270
+
assert_eq!(ops.next(), None);
271
+
}
272
+
273
+
#[test]
274
+
fn load_store() {
275
+
let state = crate::Um::new(assemble(
276
+
r#"
277
+
adr r1, loc
278
+
ldr r2, [r0, r1]
279
+
mov r3, 56
280
+
str r3, [r0, r1]
281
+
halt
282
+
loc:.u32 42
283
+
"#,
284
+
))
285
+
.run();
286
+
assert_eq!(state.registers[R2], 42);
287
+
assert_eq!(state.memory[0][5], 56);
288
+
}
289
+
290
+
#[test]
291
+
fn addition() {
292
+
let state = crate::Um::new(assemble(
293
+
r#"
294
+
mov r0, 42
295
+
mov r1, 64
296
+
mov r2, 8192
297
+
298
+
add r3, r0, r1 ; r3 = r0 + r1 = 106
299
+
add r1, r2 ; r1 = r1 + r2 = 8256
300
+
add r0 ; r0 = r0 + r0 = 84
301
+
302
+
halt
303
+
"#,
304
+
))
305
+
.run();
306
+
307
+
assert_eq!(state.registers[R0], 84);
308
+
assert_eq!(state.registers[R1], 8256);
309
+
assert_eq!(state.registers[R2], 8192);
310
+
assert_eq!(state.registers[R3], 106);
311
+
}
312
+
313
+
#[test]
314
+
fn alloc() {
315
+
let state = crate::Um::new(assemble(
316
+
r#"
317
+
; Allocate 1000 bytes.
318
+
mov r0, 1000
319
+
alloc r1, r0
320
+
halt
321
+
"#,
322
+
))
323
+
.run();
324
+
assert_eq!(state.registers[R0], 1000);
325
+
assert_ne!(state.registers[R1], 0);
326
+
assert_eq!(state.memory[state.registers[R1] as usize].len(), 1000);
327
+
}
328
+
329
+
#[test]
330
+
fn free() {
331
+
let state = crate::Um::new(assemble(
332
+
r#"
333
+
; Allocate 1000 bytes.
334
+
mov r0, 1000
335
+
alloc r1, r0
336
+
free r1
337
+
halt
338
+
"#,
339
+
))
340
+
.run();
341
+
assert_eq!(state.registers[R0], 1000);
342
+
assert_ne!(state.registers[R1], 0);
343
+
assert_eq!(
344
+
state.memory[state.registers[R1] as usize].len(),
345
+
0,
346
+
"memory not free'd"
347
+
);
348
+
}
349
+
}
+61
src/bin/uasm.rs
+61
src/bin/uasm.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
use std::path::{Path, PathBuf};
16
+
17
+
fn main() {
18
+
let mut output = PathBuf::from("./a.um");
19
+
20
+
let mut program = Vec::new();
21
+
let mut args = std::env::args().skip(1);
22
+
while let Some(arg) = args.next() {
23
+
match arg.as_str() {
24
+
"-o" | "--out" => {
25
+
output = PathBuf::from(args.next().expect("expected output path"));
26
+
}
27
+
_ => {
28
+
let path = Path::new(&arg);
29
+
program.extend_from_slice(&match load_program(path) {
30
+
Ok(p) => p,
31
+
Err(error) => {
32
+
eprintln!("{error}");
33
+
std::process::exit(1);
34
+
}
35
+
});
36
+
}
37
+
}
38
+
}
39
+
40
+
// Convert the program to bytes.
41
+
let bytes: Vec<_> = program
42
+
.into_iter()
43
+
.flat_map(|word| word.to_be_bytes())
44
+
.collect();
45
+
46
+
std::fs::write(&output, bytes).unwrap();
47
+
}
48
+
49
+
fn load_program(path: &Path) -> std::io::Result<Vec<u32>> {
50
+
match path.extension().map(|ext| ext.as_encoded_bytes()) {
51
+
Some(b"uasm") | Some(b"asm") => {
52
+
let source = std::fs::read_to_string(path)?;
53
+
let program = um::asm::assemble(&source);
54
+
Ok(program)
55
+
}
56
+
_ => {
57
+
let program = std::fs::read(path)?;
58
+
Ok(um::conv::bytes_to_program(&program).unwrap())
59
+
}
60
+
}
61
+
}
+71
src/bin/um.rs
+71
src/bin/um.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
use std::{path::Path, time::Instant};
16
+
use um::Um;
17
+
18
+
fn main() {
19
+
let mut program = Vec::new();
20
+
let mut time = false;
21
+
22
+
for arg in std::env::args().skip(1) {
23
+
if arg == "--time" {
24
+
time = true;
25
+
continue;
26
+
}
27
+
28
+
let path = Path::new(&arg);
29
+
program.extend_from_slice(&match load_program(path) {
30
+
Ok(p) => p,
31
+
Err(error) => {
32
+
eprintln!("{error}");
33
+
std::process::exit(1);
34
+
}
35
+
});
36
+
}
37
+
38
+
let start = Instant::now();
39
+
Um::new(program)
40
+
.stdout(&mut std::io::stdout())
41
+
.stdin(&mut std::io::stdin())
42
+
.run();
43
+
44
+
if time {
45
+
eprintln!("{:?}", start.elapsed());
46
+
}
47
+
}
48
+
49
+
#[cfg(feature = "asm")]
50
+
fn load_program(path: &Path) -> std::io::Result<Vec<u32>> {
51
+
match path.extension().map(|ext| ext.as_encoded_bytes()) {
52
+
// In an ideal world we would just add `#[cfg(feature = "asm")]` here.
53
+
// Unfortunately this leads some wierd code generation fuckery which
54
+
// makes the version without the 'asm' feature ~1-2 seconds slower
55
+
// when running the sandmark program.
56
+
Some(b"uasm") | Some(b"asm") => {
57
+
let source = std::fs::read_to_string(path)?;
58
+
Ok(um::asm::assemble(&source))
59
+
}
60
+
_ => {
61
+
let program = std::fs::read(path)?;
62
+
Ok(um::conv::bytes_to_program(&program).unwrap())
63
+
}
64
+
}
65
+
}
66
+
67
+
#[cfg(not(feature = "asm"))]
68
+
fn load_program(path: &Path) -> std::io::Result<Vec<u32>> {
69
+
let program = std::fs::read(path)?;
70
+
Ok(um::conv::bytes_to_program(&program).unwrap())
71
+
}
+32
src/conv.rs
+32
src/conv.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
const WORD_LEN: usize = std::mem::size_of::<u32>();
16
+
17
+
#[derive(Debug)]
18
+
pub struct InvalidProgram;
19
+
20
+
/// Converts a byte slice to a program.
21
+
///
22
+
/// Returns `None` if the byte slice is not a multiple of 4 bytes in length.
23
+
pub fn bytes_to_program(bytes: &[u8]) -> Result<Vec<u32>, InvalidProgram> {
24
+
if bytes.len().rem_euclid(WORD_LEN) != 0 {
25
+
return Err(InvalidProgram);
26
+
}
27
+
28
+
Ok(bytes
29
+
.chunks_exact(WORD_LEN)
30
+
.map(|word| u32::from_be_bytes(word.try_into().unwrap()))
31
+
.collect())
32
+
}
+352
src/lib.rs
+352
src/lib.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
use smallvec::SmallVec;
16
+
use std::io::{Read, Write};
17
+
18
+
#[cfg(feature = "asm")]
19
+
pub mod asm;
20
+
pub mod conv;
21
+
pub mod ops;
22
+
pub mod reg;
23
+
24
+
use ops::Operation;
25
+
use reg::{Page, Register};
26
+
27
+
const SMALLVEC_SIZE: usize = 24;
28
+
29
+
/// Lossless conversion to `usize`.
30
+
///
31
+
/// This should only be implemented on types which can be losslessly
32
+
/// cast to a `usize`.
33
+
trait IntoIndex: Sized + Copy {
34
+
fn into_index(self) -> usize;
35
+
}
36
+
37
+
macro_rules! impl_into_index {
38
+
($t:ty) => {
39
+
impl IntoIndex for $t {
40
+
fn into_index(self) -> usize {
41
+
self as usize
42
+
}
43
+
}
44
+
};
45
+
}
46
+
47
+
#[cfg(target_pointer_width = "16")]
48
+
compile_error!("16 bit architectures are unsupported");
49
+
50
+
// usize *may* be 16 bits, so only implement if it is 32 or 64 bits.
51
+
#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))]
52
+
impl_into_index!(u32);
53
+
54
+
#[derive(Default)]
55
+
pub struct Um<'a> {
56
+
pub program_counter: u32,
57
+
registers: Page,
58
+
/// Program memory, modelled as a `Vec` of `SmallVec`.
59
+
///
60
+
/// Memory allocations greater than `SMALLVEC_SIZE` will incur a memory
61
+
/// indirection penalty for every memory access within that block.
62
+
memory: Vec<SmallVec<[u32; SMALLVEC_SIZE]>>,
63
+
free_blocks: Vec<u32>,
64
+
/// Partially decoded operations cache.
65
+
ops: Vec<Operation>,
66
+
stdin: Option<&'a mut dyn Read>,
67
+
stdout: Option<&'a mut dyn Write>,
68
+
}
69
+
70
+
impl<'a> Um<'a> {
71
+
/// Initialise a Universal Machine with the specified program scroll.
72
+
pub fn new(program: Vec<u32>) -> Self {
73
+
let ops = ops::decode(&program);
74
+
Self {
75
+
memory: vec![program.into()],
76
+
ops,
77
+
..Default::default()
78
+
}
79
+
}
80
+
81
+
/// Sets the output for the universal machine.
82
+
pub fn stdout<T: Write>(mut self, stdout: &'a mut T) -> Self {
83
+
self.stdout.replace(stdout);
84
+
self
85
+
}
86
+
87
+
/// Sets the input for the universal machine.
88
+
pub fn stdin<T: Read>(mut self, stdin: &'a mut T) -> Self {
89
+
self.stdin.replace(stdin);
90
+
self
91
+
}
92
+
93
+
/// Begins the spin-cycle of the universal machine.
94
+
#[inline(never)]
95
+
pub fn run(mut self) -> Self {
96
+
loop {
97
+
// println!(
98
+
// "{:?}, pc: {:08x}, r: {:08x?}",
99
+
// self.ops[self.program_counter as usize], self.program_counter, self.registers
100
+
// );
101
+
match self.ops[self.program_counter as usize] {
102
+
Operation::ConditionalMove { a, b, c } => self.conditional_move(a, b, c),
103
+
Operation::ArrayIndex { a, b, c } => self.array_index(a, b, c),
104
+
Operation::ArrayAmendment { a, b, c } => self.array_amendment(a, b, c),
105
+
Operation::Addition { a, b, c } => self.addition(a, b, c),
106
+
Operation::Multiplication { a, b, c } => self.multiplication(a, b, c),
107
+
Operation::Division { a, b, c } => self.division(a, b, c),
108
+
Operation::NotAnd { a, b, c } => self.not_and(a, b, c),
109
+
Operation::Halt => break,
110
+
Operation::Allocation { b, c } => self.allocation(b, c),
111
+
Operation::Abandonment { c } => self.abandonment(c),
112
+
Operation::Output { c } => self.output(c),
113
+
Operation::Input { c } => self.input(c),
114
+
Operation::LoadProgram { b, c } => {
115
+
self.load_program(b, c);
116
+
continue;
117
+
}
118
+
Operation::Orthography { a, value } => self.orthography(a, value),
119
+
Operation::IllegalInstruction => self.illegal_instruction(),
120
+
}
121
+
self.program_counter += 1;
122
+
}
123
+
124
+
self
125
+
}
126
+
127
+
// Un-commenting step() slows down the sandmark benchmark by ~3-5 seconds, even
128
+
// though it has *no* interaction with the code path in Um::run().
129
+
//
130
+
// /// Steps one instruction.
131
+
// #[inline(never)]
132
+
// pub fn step(&mut self) -> bool {
133
+
// match self.ops[self.program_counter as usize] {
134
+
// Operation::ConditionalMove { a, b, c } => self.conditional_move(a, b, c),
135
+
// Operation::ArrayIndex { a, b, c } => self.array_index(a, b, c),
136
+
// Operation::ArrayAmendment { a, b, c } => self.array_amendment(a, b, c),
137
+
// Operation::Addition { a, b, c } => self.addition(a, b, c),
138
+
// Operation::Multiplication { a, b, c } => self.multiplication(a, b, c),
139
+
// Operation::Division { a, b, c } => self.division(a, b, c),
140
+
// Operation::NotAnd { a, b, c } => self.not_and(a, b, c),
141
+
// Operation::Halt => return false,
142
+
// Operation::Allocation { b, c } => self.allocation(b, c),
143
+
// Operation::Abandonment { c } => self.abandonment(c),
144
+
// Operation::Output { c } => self.output(c),
145
+
// Operation::Input { c } => self.input(c),
146
+
// Operation::LoadProgram { b, c } => {
147
+
// self.load_program(b, c);
148
+
// return true;
149
+
// }
150
+
// Operation::Orthography { a, value } => self.orthography(a, value),
151
+
// Operation::IllegalInstruction => self.illegal_instruction(),
152
+
// }
153
+
// self.program_counter += 1;
154
+
// true
155
+
// }
156
+
157
+
/// Loads the value from the specified register.
158
+
fn load_register(&self, register: Register) -> u32 {
159
+
self.registers[register]
160
+
}
161
+
162
+
/// Saves a value to the specified register.
163
+
fn save_register(&mut self, register: Register, value: u32) {
164
+
self.registers[register] = value;
165
+
}
166
+
167
+
fn conditional_move(&mut self, a: Register, b: Register, c: Register) {
168
+
if self.load_register(c) != 0 {
169
+
self.save_register(a, self.load_register(b));
170
+
}
171
+
}
172
+
173
+
fn array_index(&mut self, a: Register, b: Register, c: Register) {
174
+
let block = self.load_register(b);
175
+
let offset = self.load_register(c);
176
+
self.save_register(a, self.load_memory(block, offset));
177
+
}
178
+
179
+
fn array_amendment(&mut self, a: Register, b: Register, c: Register) {
180
+
let block = self.load_register(a);
181
+
let offset = self.load_register(b);
182
+
let value = self.load_register(c);
183
+
self.store_memory(block, offset, value);
184
+
}
185
+
186
+
fn addition(&mut self, a: Register, b: Register, c: Register) {
187
+
self.save_register(a, self.load_register(b).wrapping_add(self.load_register(c)));
188
+
}
189
+
190
+
fn multiplication(&mut self, a: Register, b: Register, c: Register) {
191
+
self.save_register(a, self.load_register(b).wrapping_mul(self.load_register(c)));
192
+
}
193
+
194
+
fn division(&mut self, a: Register, b: Register, c: Register) {
195
+
self.save_register(a, self.load_register(b).wrapping_div(self.load_register(c)));
196
+
}
197
+
198
+
fn not_and(&mut self, a: Register, b: Register, c: Register) {
199
+
self.save_register(a, !(self.load_register(b) & self.load_register(c)));
200
+
}
201
+
202
+
fn allocation(&mut self, b: Register, c: Register) {
203
+
let length = self.load_register(c);
204
+
let index = self.allocate_memory(length);
205
+
self.save_register(b, index);
206
+
}
207
+
208
+
fn abandonment(&mut self, c: Register) {
209
+
let block = self.load_register(c);
210
+
self.free_memory(block);
211
+
}
212
+
213
+
fn output(&mut self, c: Register) {
214
+
let value = self.load_register(c);
215
+
if let Some(stdout) = self.stdout.as_mut() {
216
+
let buffer = [(value & 0xff) as u8];
217
+
stdout.write_all(&buffer).unwrap();
218
+
}
219
+
}
220
+
221
+
fn input(&mut self, c: Register) {
222
+
if let Some(stdin) = self.stdin.as_mut() {
223
+
let mut buffer = vec![0];
224
+
match stdin.read_exact(&mut buffer) {
225
+
Ok(()) => self.save_register(c, buffer[0] as u32),
226
+
Err(_) => self.save_register(c, u32::MAX),
227
+
}
228
+
} else {
229
+
self.save_register(c, u32::MAX);
230
+
}
231
+
}
232
+
233
+
fn load_program(&mut self, b: Register, c: Register) {
234
+
let block = self.load_register(b);
235
+
236
+
// Source array is always copied to array[0], but there
237
+
// is no point copying array[0] to array[0].
238
+
if block != 0 {
239
+
let duplicated = self.duplicate_memory(block);
240
+
let ops = ops::decode(duplicated);
241
+
self.ops = ops;
242
+
}
243
+
244
+
self.program_counter = self.load_register(c);
245
+
}
246
+
247
+
fn orthography(&mut self, a: Register, value: u32) {
248
+
self.save_register(a, value);
249
+
}
250
+
251
+
#[cold]
252
+
#[inline(never)]
253
+
fn illegal_instruction(&self) -> ! {
254
+
panic!(
255
+
"illegal instruction: {:08x}, pc: {:08x}, r: {:08x?}",
256
+
self.memory[0][self.program_counter.into_index()],
257
+
self.program_counter,
258
+
self.registers
259
+
)
260
+
}
261
+
262
+
fn load_memory(&self, block: u32, offset: u32) -> u32 {
263
+
let block = block.into_index();
264
+
let offset = offset.into_index();
265
+
assert!(block < self.memory.len() && offset < self.memory[block].len());
266
+
self.memory[block][offset]
267
+
}
268
+
269
+
fn store_memory(&mut self, block: u32, offset: u32, value: u32) {
270
+
let block = block.into_index();
271
+
let offset = offset.into_index();
272
+
assert!(block < self.memory.len() && offset < self.memory[block].len());
273
+
self.memory[block][offset] = value
274
+
}
275
+
276
+
/// Duplicates a block of memory.
277
+
///
278
+
/// The block is copied to the first block of memory.
279
+
fn duplicate_memory(&mut self, block: u32) -> &[u32] {
280
+
let block = block.into_index();
281
+
assert!(block < self.memory.len());
282
+
self.memory[0] = self.memory[block].clone();
283
+
&self.memory[0]
284
+
}
285
+
286
+
/// Allocates a block of memory of the specified length.
287
+
fn allocate_memory(&mut self, length: u32) -> u32 {
288
+
if let Some(index) = self.free_blocks.pop() {
289
+
self.memory[index.into_index()] = Self::new_block(length.into_index());
290
+
index
291
+
} else {
292
+
self.memory.push(Self::new_block(length.into_index()));
293
+
(self.memory.len() - 1) as u32
294
+
}
295
+
}
296
+
297
+
/// Frees a block of memory.
298
+
fn free_memory(&mut self, block: u32) {
299
+
assert!(block.into_index() < self.memory.len());
300
+
self.free_blocks.push(block);
301
+
self.memory[block.into_index()] = Self::new_block(0);
302
+
}
303
+
304
+
/// Creates a new block of memory.
305
+
///
306
+
/// The block is initialised with `len` zeroes.
307
+
fn new_block(len: usize) -> SmallVec<[u32; SMALLVEC_SIZE]> {
308
+
smallvec::smallvec![0; len]
309
+
}
310
+
}
311
+
312
+
#[cfg(test)]
313
+
mod tests {
314
+
use super::*;
315
+
316
+
#[test]
317
+
#[should_panic]
318
+
fn empty_program() {
319
+
Um::new(vec![]).run();
320
+
}
321
+
322
+
#[test]
323
+
fn just_halt() {
324
+
Um::new(vec![0x70000000]).run();
325
+
}
326
+
327
+
#[test]
328
+
#[cfg(feature = "asm")]
329
+
fn hello_world() {
330
+
let program = asm::assemble(include_str!("../files/hello-world.asm"));
331
+
let mut buffer = Vec::new();
332
+
Um::new(program).stdout(&mut buffer).run();
333
+
assert_eq!(&buffer, b"Hello, world!\n");
334
+
}
335
+
336
+
#[test]
337
+
#[cfg(feature = "asm")]
338
+
fn cat() {
339
+
let program = asm::assemble(include_str!("../files/cat.asm"));
340
+
let input = include_bytes!("lib.rs");
341
+
342
+
let mut reader = std::io::Cursor::new(input);
343
+
let mut buffer = Vec::new();
344
+
345
+
Um::new(program)
346
+
.stdin(&mut reader)
347
+
.stdout(&mut buffer)
348
+
.run();
349
+
350
+
assert_eq!(&buffer, &input);
351
+
}
352
+
}
-404
src/main.rs
-404
src/main.rs
···
1
-
fn main() {
2
-
let mut program = Vec::new();
3
-
for arg in std::env::args().skip(1) {
4
-
let p = std::fs::read(arg).unwrap();
5
-
program.extend_from_slice(&p);
6
-
}
7
-
8
-
Um::from_bytes(program)
9
-
.stdout(std::io::stdout())
10
-
.stdin(std::io::stdin())
11
-
.run();
12
-
}
13
-
14
-
type Platter = u32;
15
-
type Register = u8;
16
-
17
-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18
-
enum Op {
19
-
ConditionalMove {
20
-
a: Register,
21
-
b: Register,
22
-
c: Register,
23
-
},
24
-
ArrayIndex {
25
-
a: Register,
26
-
b: Register,
27
-
c: Register,
28
-
},
29
-
ArrayAmendment {
30
-
a: Register,
31
-
b: Register,
32
-
c: Register,
33
-
},
34
-
Addition {
35
-
a: Register,
36
-
b: Register,
37
-
c: Register,
38
-
},
39
-
Multiplication {
40
-
a: Register,
41
-
b: Register,
42
-
c: Register,
43
-
},
44
-
Division {
45
-
a: Register,
46
-
b: Register,
47
-
c: Register,
48
-
},
49
-
NotAnd {
50
-
a: Register,
51
-
b: Register,
52
-
c: Register,
53
-
},
54
-
Halt,
55
-
Allocation {
56
-
b: Register,
57
-
c: Register,
58
-
},
59
-
Abandonment {
60
-
c: Register,
61
-
},
62
-
Output {
63
-
c: Register,
64
-
},
65
-
Input {
66
-
c: Register,
67
-
},
68
-
LoadProgram {
69
-
b: Register,
70
-
c: Register,
71
-
},
72
-
Orthography {
73
-
a: Register,
74
-
value: u32,
75
-
},
76
-
}
77
-
78
-
impl From<Platter> for Op {
79
-
fn from(value: Platter) -> Self {
80
-
let a = ((value >> 6) & 0x07) as Register;
81
-
let b = ((value >> 3) & 0x07) as Register;
82
-
let c = ((value >> 0) & 0x07) as Register;
83
-
84
-
match value & 0xf0000000 {
85
-
0x00000000 => Self::ConditionalMove { a, b, c },
86
-
0x10000000 => Self::ArrayIndex { a, b, c },
87
-
0x20000000 => Self::ArrayAmendment { a, b, c },
88
-
0x30000000 => Self::Addition { a, b, c },
89
-
0x40000000 => Self::Multiplication { a, b, c },
90
-
0x50000000 => Self::Division { a, b, c },
91
-
0x60000000 => Self::NotAnd { a, b, c },
92
-
0x70000000 => Self::Halt,
93
-
0x80000000 => Self::Allocation { b, c },
94
-
0x90000000 => Self::Abandonment { c },
95
-
0xa0000000 => Self::Output { c },
96
-
0xb0000000 => Self::Input { c },
97
-
0xc0000000 => Self::LoadProgram { b, c },
98
-
0xd0000000 => {
99
-
let a = ((value >> 25) & 0x07) as Register;
100
-
let value = value & 0x01ffffff;
101
-
Self::Orthography { a, value }
102
-
}
103
-
_ => Self::Halt,
104
-
}
105
-
}
106
-
}
107
-
108
-
fn decode_ops(ops: &[Platter]) -> Vec<Op> {
109
-
ops.iter().map(|encoded| Op::from(*encoded)).collect()
110
-
}
111
-
112
-
#[derive(Default)]
113
-
pub struct Um {
114
-
program_counter: Platter,
115
-
registers: [Platter; 8],
116
-
memory: Vec<Vec<Platter>>,
117
-
#[cfg(feature = "reclaim-memory")]
118
-
free_blocks: Vec<Platter>,
119
-
ops: Vec<Op>,
120
-
stdin: Option<Box<dyn std::io::Read>>,
121
-
stdout: Option<Box<dyn std::io::Write>>,
122
-
}
123
-
124
-
impl Um {
125
-
/// Construct a new Universal Machine with the specified program.
126
-
pub fn new(program: Vec<Platter>) -> Self {
127
-
let ops = decode_ops(&program);
128
-
Self {
129
-
memory: vec![program],
130
-
ops,
131
-
..Default::default()
132
-
}
133
-
}
134
-
135
-
/// Construct a new Universal Machine from a program represented in bytes.
136
-
pub fn from_bytes(program: impl AsRef<[u8]>) -> Self {
137
-
let bytes = program.as_ref();
138
-
let mut program = Vec::with_capacity(bytes.len().div_ceil(size_of::<Platter>()));
139
-
140
-
// Split the program into platters.
141
-
let mut chunks = bytes.chunks_exact(size_of::<Platter>());
142
-
for word in &mut chunks {
143
-
program.push(Platter::from_be_bytes([word[0], word[1], word[2], word[3]]));
144
-
}
145
-
146
-
if !chunks.remainder().is_empty() {
147
-
eprintln!(
148
-
"WARNING: program may be corrupt; {} bytes remain after platter conversion.",
149
-
chunks.remainder().len()
150
-
);
151
-
}
152
-
153
-
Self::new(program)
154
-
}
155
-
156
-
/// Sets the output for the univeral machine.
157
-
pub fn stdout(mut self, stdout: impl std::io::Write + 'static) -> Self {
158
-
self.stdout.replace(Box::new(stdout));
159
-
self
160
-
}
161
-
162
-
/// Sets the input for the universal machine.
163
-
pub fn stdin(mut self, stdin: impl std::io::Read + 'static) -> Self {
164
-
self.stdin.replace(Box::new(stdin));
165
-
self
166
-
}
167
-
168
-
/// Begins the spin-cycle of the universal machine.
169
-
pub fn run(mut self) {
170
-
loop {
171
-
match self.ops[self.program_counter as usize] {
172
-
// Operator #0. Conditional Move.
173
-
//
174
-
// The register A receives the value in register B,
175
-
// unless the register C contains 0.
176
-
Op::ConditionalMove { a, b, c } => {
177
-
if self.load_register(c) != 0 {
178
-
self.save_register(a, self.load_register(b));
179
-
}
180
-
}
181
-
182
-
// Operator #1: Array Index.
183
-
//
184
-
// The register A receives the value stored at offset
185
-
// in register C in the array identified by B.
186
-
Op::ArrayIndex { a, b, c } => {
187
-
let block = self.load_register(b);
188
-
let offset = self.load_register(c);
189
-
self.save_register(a, self.load_memory(block, offset));
190
-
}
191
-
192
-
// Operator #2. Array Amendment.
193
-
//
194
-
// The array identified by A is amended at the offset
195
-
// in register B to store the value in register C.
196
-
Op::ArrayAmendment { a, b, c } => {
197
-
let block = self.load_register(a);
198
-
let offset = self.load_register(b);
199
-
let value = self.load_register(c);
200
-
self.store_memory(block, offset, value);
201
-
}
202
-
203
-
// Operator #3. Addition.
204
-
//
205
-
// The register A receives the value in register B plus
206
-
// the value in register C, modulo 2^32.
207
-
Op::Addition { a, b, c } => {
208
-
self.save_register(
209
-
a,
210
-
self.load_register(b).wrapping_add(self.load_register(c)),
211
-
);
212
-
}
213
-
214
-
// Operator #4. Multiplication.
215
-
//
216
-
// The register A receives the value in register B times
217
-
// the value in register C, modulo 2^32.
218
-
Op::Multiplication { a, b, c } => {
219
-
self.save_register(
220
-
a,
221
-
self.load_register(b).wrapping_mul(self.load_register(c)),
222
-
);
223
-
}
224
-
225
-
// Operator #5. Division.
226
-
//
227
-
// The register A receives the value in register B
228
-
// divided by the value in register C, if any, where
229
-
// each quantity is treated as an unsigned 32 bit number.
230
-
Op::Division { a, b, c } => {
231
-
self.save_register(
232
-
a,
233
-
self.load_register(b).wrapping_div(self.load_register(c)),
234
-
);
235
-
}
236
-
237
-
// Operator #6. Not-And.
238
-
//
239
-
// Each bit in the register A receives the 1 bit if
240
-
// either register B or register C has a 0 bit in that
241
-
// position. Otherwise the bit in register A receives
242
-
// the 0 bit.
243
-
Op::NotAnd { a, b, c } => {
244
-
self.save_register(a, !(self.load_register(b) & self.load_register(c)));
245
-
}
246
-
247
-
// Operator #7. Halt.
248
-
//
249
-
// The universal machine stops computation.
250
-
Op::Halt => break,
251
-
252
-
// Operator #8. Allocation.
253
-
//
254
-
// A new array is created with a capacity of platters
255
-
// commensurate to the value in the register C. This
256
-
// new array is initialized entirely with platters
257
-
// holding the value 0. A bit pattern not consisting of
258
-
// exclusively the 0 bit, and that identifies no other
259
-
// active allocated array, is placed in the B register.
260
-
Op::Allocation { b, c } => {
261
-
let length = self.load_register(c);
262
-
let index = self.allocate_memory(length);
263
-
self.save_register(b, index);
264
-
}
265
-
266
-
// Operator #9. Abandonment.
267
-
//
268
-
// The array identified by the register C is abandoned.
269
-
// Future allocations may then reuse that identifier.
270
-
Op::Abandonment { c } => {
271
-
let block = self.load_register(c);
272
-
self.free_memory(block);
273
-
}
274
-
275
-
// Operator #10. Output.
276
-
//
277
-
// The value in the register C is displayed on the console
278
-
// immediately. Only values between and including 0 and 255
279
-
// are allowed.
280
-
Op::Output { c } => {
281
-
let value = self.load_register(c);
282
-
if let Some(stdout) = self.stdout.as_mut() {
283
-
let buffer = [(value & 0xff) as u8];
284
-
stdout.write_all(&buffer).unwrap();
285
-
}
286
-
}
287
-
288
-
// Operator #11. Input.
289
-
//
290
-
// The universal machine waits for input on the console.
291
-
// When input arrives, the register C is loaded with the
292
-
// input, which must be between and including 0 and 255.
293
-
// If the end of input has been signaled, then the
294
-
// register C is endowed with a uniform value pattern
295
-
// where every place is pregnant with the 1 bit.
296
-
Op::Input { c } => {
297
-
if let Some(stdin) = self.stdin.as_mut() {
298
-
let mut buffer = vec![0];
299
-
match stdin.read_exact(&mut buffer) {
300
-
Ok(()) => self.save_register(c, buffer[0] as u32),
301
-
Err(_) => self.save_register(c, 0xff),
302
-
}
303
-
} else {
304
-
self.save_register(c, 0xff);
305
-
}
306
-
}
307
-
308
-
// Operator #12. Load Program.
309
-
//
310
-
// The array identified by the B register is duplicated
311
-
// and the duplicate shall replace the '0' array,
312
-
// regardless of size. The execution finger is placed
313
-
// to indicate the platter of this array that is
314
-
// described by the offset given in C, where the value
315
-
// 0 denotes the first platter, 1 the second, et
316
-
// cetera.
317
-
//
318
-
// The '0' array shall be the most sublime choice for
319
-
// loading, and shall be handled with the utmost
320
-
// velocity.
321
-
Op::LoadProgram { b, c } => {
322
-
let block = self.load_register(b);
323
-
324
-
// Source array is always copied to array[0], but there
325
-
// is no point copying array[0] to array[0].
326
-
if block != 0 {
327
-
let duplicated = self.duplicate_memory(block);
328
-
let ops = decode_ops(&duplicated);
329
-
self.ops = ops;
330
-
}
331
-
332
-
self.program_counter = self.load_register(c);
333
-
continue;
334
-
}
335
-
336
-
// Operator #13. Orthography.
337
-
//
338
-
// The value indicated is loaded into the register A
339
-
// forthwith.
340
-
Op::Orthography { a, value } => {
341
-
self.save_register(a, value);
342
-
}
343
-
}
344
-
345
-
self.program_counter += 1;
346
-
}
347
-
}
348
-
349
-
/// Loads the value from the specified register.
350
-
fn load_register(&self, index: Register) -> Platter {
351
-
debug_assert!(index < 8, "register index out of bounds");
352
-
self.registers[index as usize]
353
-
}
354
-
355
-
/// Saves a value to the specified register.
356
-
fn save_register(&mut self, index: Register, value: Platter) {
357
-
debug_assert!(index < 8, "register index out of bounds");
358
-
self.registers[index as usize] = value;
359
-
}
360
-
361
-
fn load_memory(&self, block: Platter, offset: Platter) -> Platter {
362
-
debug_assert!((block as usize) < self.memory.len());
363
-
debug_assert!((offset as usize) < self.memory[block as usize].len());
364
-
self.memory[block as usize][offset as usize]
365
-
}
366
-
367
-
fn store_memory(&mut self, block: Platter, offset: Platter, value: Platter) {
368
-
debug_assert!((block as usize) < self.memory.len());
369
-
debug_assert!((offset as usize) < self.memory[block as usize].len());
370
-
self.memory[block as usize][offset as usize] = value;
371
-
}
372
-
373
-
fn duplicate_memory(&mut self, block: Platter) -> &[Platter] {
374
-
debug_assert!((block as usize) < self.memory.len());
375
-
self.memory[0] = self.memory[block as usize].clone();
376
-
&self.memory[0]
377
-
}
378
-
379
-
#[cfg(not(feature = "reclaim-memory"))]
380
-
fn allocate_memory(&mut self, length: Platter) -> Platter {
381
-
self.memory.push(vec![0; length as usize]);
382
-
(self.memory.len() - 1) as Platter
383
-
}
384
-
385
-
#[cfg(feature = "reclaim-memory")]
386
-
fn allocate_memory(&mut self, length: Platter) -> Platter {
387
-
if let Some(index) = self.free_blocks.pop() {
388
-
self.memory[index as usize] = vec![0; length as usize];
389
-
index as Platter
390
-
} else {
391
-
self.memory.push(vec![0; length as usize]);
392
-
(self.memory.len() - 1) as Platter
393
-
}
394
-
}
395
-
396
-
fn free_memory(&mut self, block: Platter) {
397
-
debug_assert!((block as usize) < self.memory.len());
398
-
#[cfg(feature = "reclaim-memory")]
399
-
{
400
-
self.free_blocks.push(block);
401
-
self.memory[block as usize] = vec![];
402
-
}
403
-
}
404
-
}
+188
src/ops.rs
+188
src/ops.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
use crate::reg::Register;
16
+
17
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18
+
pub enum Operation {
19
+
/// Operator #0. Conditional Move.
20
+
///
21
+
/// The register A receives the value in register B,
22
+
/// unless the register C contains 0.
23
+
ConditionalMove {
24
+
a: Register,
25
+
b: Register,
26
+
c: Register,
27
+
},
28
+
/// Operator #1: Array Index.
29
+
///
30
+
/// The register A receives the value stored at offset
31
+
/// in register C in the array identified by B.
32
+
ArrayIndex {
33
+
a: Register,
34
+
b: Register,
35
+
c: Register,
36
+
},
37
+
/// Operator #2. Array Amendment.
38
+
///
39
+
/// The array identified by A is amended at the offset
40
+
/// in register B to store the value in register C.
41
+
ArrayAmendment {
42
+
a: Register,
43
+
b: Register,
44
+
c: Register,
45
+
},
46
+
/// Operator #3. Addition.
47
+
///
48
+
/// The register A receives the value in register B plus
49
+
/// the value in register C, modulo 2^32.
50
+
Addition {
51
+
a: Register,
52
+
b: Register,
53
+
c: Register,
54
+
},
55
+
/// Operator #4. Multiplication.
56
+
///
57
+
/// The register A receives the value in register B times
58
+
/// the value in register C, modulo 2^32.
59
+
Multiplication {
60
+
a: Register,
61
+
b: Register,
62
+
c: Register,
63
+
},
64
+
/// Operator #5. Division.
65
+
///
66
+
/// The register A receives the value in register B
67
+
/// divided by the value in register C, if any, where
68
+
/// each quantity is treated as an unsigned 32 bit number.
69
+
Division {
70
+
a: Register,
71
+
b: Register,
72
+
c: Register,
73
+
},
74
+
/// Operator #6. Not-And.
75
+
///
76
+
/// Each bit in the register A receives the 1 bit if
77
+
/// either register B or register C has a 0 bit in that
78
+
/// position. Otherwise the bit in register A receives
79
+
/// the 0 bit.
80
+
NotAnd {
81
+
a: Register,
82
+
b: Register,
83
+
c: Register,
84
+
},
85
+
/// Operator #7. Halt.
86
+
///
87
+
/// The universal machine stops computation.
88
+
Halt,
89
+
/// Operator #8. Allocation.
90
+
///
91
+
/// A new array is created with a capacity of platters
92
+
/// commensurate to the value in the register C. This
93
+
/// new array is initialized entirely with platters
94
+
/// holding the value 0. A bit pattern not consisting of
95
+
/// exclusively the 0 bit, and that identifies no other
96
+
/// active allocated array, is placed in the B register.
97
+
Allocation {
98
+
b: Register,
99
+
c: Register,
100
+
},
101
+
/// Operator #9. Abandonment.
102
+
///
103
+
/// The array identified by the register C is abandoned.
104
+
/// Future allocations may then reuse that identifier.
105
+
Abandonment {
106
+
c: Register,
107
+
},
108
+
/// Operator #10. Output.
109
+
///
110
+
/// The value in the register C is displayed on the console
111
+
/// immediately. Only values between and including 0 and 255
112
+
/// are allowed.
113
+
Output {
114
+
c: Register,
115
+
},
116
+
/// Operator #11. Input.
117
+
///
118
+
/// The universal machine waits for input on the console.
119
+
/// When input arrives, the register C is loaded with the
120
+
/// input, which must be between and including 0 and 255.
121
+
/// If the end of input has been signaled, then the
122
+
/// register C is endowed with a uniform value pattern
123
+
/// where every place is pregnant with the 1 bit.
124
+
Input {
125
+
c: Register,
126
+
},
127
+
/// Operator #12. Load Program.
128
+
///
129
+
/// The array identified by the B register is duplicated
130
+
/// and the duplicate shall replace the '0' array,
131
+
/// regardless of size. The execution finger is placed
132
+
/// to indicate the platter of this array that is
133
+
/// described by the offset given in C, where the value
134
+
/// 0 denotes the first platter, 1 the second, et
135
+
/// cetera.
136
+
///
137
+
/// The '0' array shall be the most sublime choice for
138
+
/// loading, and shall be handled with the utmost
139
+
/// velocity.
140
+
LoadProgram {
141
+
b: Register,
142
+
c: Register,
143
+
},
144
+
/// Operator #13. Orthography.
145
+
///
146
+
/// The value indicated is loaded into the register A
147
+
/// forthwith.
148
+
Orthography {
149
+
a: Register,
150
+
value: u32,
151
+
},
152
+
IllegalInstruction,
153
+
}
154
+
155
+
impl From<u32> for Operation {
156
+
fn from(value: u32) -> Self {
157
+
let a = Register::from_u8(((value >> 6) & 0x07) as u8);
158
+
let b = Register::from_u8(((value >> 3) & 0x07) as u8);
159
+
let c = Register::from_u8((value & 0x07) as u8);
160
+
match value & 0xf0000000 {
161
+
0x00000000 => Self::ConditionalMove { a, b, c },
162
+
0x10000000 => Self::ArrayIndex { a, b, c },
163
+
0x20000000 => Self::ArrayAmendment { a, b, c },
164
+
0x30000000 => Self::Addition { a, b, c },
165
+
0x40000000 => Self::Multiplication { a, b, c },
166
+
0x50000000 => Self::Division { a, b, c },
167
+
0x60000000 => Self::NotAnd { a, b, c },
168
+
0x70000000 => Self::Halt,
169
+
0x80000000 => Self::Allocation { b, c },
170
+
0x90000000 => Self::Abandonment { c },
171
+
0xa0000000 => Self::Output { c },
172
+
0xb0000000 => Self::Input { c },
173
+
0xc0000000 => Self::LoadProgram { b, c },
174
+
0xd0000000 => {
175
+
let a = Register::from_u8(((value >> 25) & 0x07) as u8);
176
+
let value = value & 0x01ffffff;
177
+
Self::Orthography { a, value }
178
+
}
179
+
_ => Self::IllegalInstruction,
180
+
}
181
+
}
182
+
}
183
+
184
+
pub fn decode(ops: &[u32]) -> Vec<Operation> {
185
+
ops.iter()
186
+
.map(|&encoded| Operation::from(encoded))
187
+
.collect()
188
+
}
+100
src/reg.rs
+100
src/reg.rs
···
1
+
// Copyright (C) 2025 Thom Hayward.
2
+
//
3
+
// This program is free software: you can redistribute it and/or modify it under
4
+
// the terms of the GNU General Public License as published by the Free Software
5
+
// Foundation, version 3.
6
+
//
7
+
// This program is distributed in the hope that it will be useful, but WITHOUT
8
+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10
+
// details.
11
+
//
12
+
// You should have received a copy of the GNU General Public License along with
13
+
// this program. If not, see <https://www.gnu.org/licenses/>.
14
+
//
15
+
/// A reference to a register of the UM-32.
16
+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
17
+
pub enum Register {
18
+
#[default]
19
+
R0,
20
+
R1,
21
+
R2,
22
+
R3,
23
+
R4,
24
+
R5,
25
+
R6,
26
+
R7,
27
+
}
28
+
29
+
impl std::fmt::Display for Register {
30
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31
+
write!(f, "r{}", *self as u8)
32
+
}
33
+
}
34
+
35
+
impl Register {
36
+
/// Encodes the register as the 'a' parameter of an encoded
37
+
/// instruction (bits 6..=8).
38
+
pub fn encode_a(self) -> u32 {
39
+
((self as u32) & 0x7) << 6
40
+
}
41
+
42
+
/// Encodes the register as the 'b' parameter of an encoded
43
+
/// instruction (bits 3..=5).
44
+
pub fn encode_b(self) -> u32 {
45
+
((self as u32) & 0x7) << 3
46
+
}
47
+
48
+
/// Encodes the register as the 'c' parameter of an encoded
49
+
/// instruction (bits 0..=2).
50
+
pub fn encode_c(self) -> u32 {
51
+
(self as u32) & 0x7
52
+
}
53
+
54
+
/// Encodes the register as the 'a' parameter of an `Orthography`
55
+
/// operation.
56
+
///
57
+
/// This is *only* valid for `Orthography` operations.
58
+
pub fn encode_a_ortho(self) -> u32 {
59
+
((self as u32) & 0x7) << 25
60
+
}
61
+
62
+
pub fn from_u8(index: u8) -> Self {
63
+
match index {
64
+
0 => Register::R0,
65
+
1 => Register::R1,
66
+
2 => Register::R2,
67
+
3 => Register::R3,
68
+
4 => Register::R4,
69
+
5 => Register::R5,
70
+
6 => Register::R6,
71
+
7 => Register::R7,
72
+
_ => unreachable!(),
73
+
}
74
+
}
75
+
}
76
+
77
+
/// A set of registers.
78
+
#[derive(Debug, Default)]
79
+
pub struct Page([u32; 8]);
80
+
81
+
impl std::ops::Index<Register> for Page {
82
+
type Output = u32;
83
+
#[inline(always)]
84
+
fn index(&self, index: Register) -> &Self::Output {
85
+
&self.0[index as usize]
86
+
}
87
+
}
88
+
89
+
impl std::ops::IndexMut<Register> for Page {
90
+
#[inline(always)]
91
+
fn index_mut(&mut self, index: Register) -> &mut Self::Output {
92
+
&mut self.0[index as usize]
93
+
}
94
+
}
95
+
96
+
impl From<[u32; 8]> for Page {
97
+
fn from(value: [u32; 8]) -> Self {
98
+
Self(value)
99
+
}
100
+
}