Compare commits

...

7 Commits

4 changed files with 118 additions and 196 deletions

View File

@ -28,8 +28,9 @@ implement the following concepts as {coloring computers}:
## tutorial
* (re)download uxn, set up dev pipeline
* check new instructions? and check changes in opcodes - for assembled rom in day 1
### day 2 and onwards
* remove square brackets in devices?
* recap how to use learn-uxn for the programs there
* check current color theme: Background/alpha, Selection, Foreground, Application
* update images if needed
* check blend mode 0
@ -41,6 +42,9 @@ implement the following concepts as {coloring computers}:
=> https://lists.sr.ht/~rabbits/uxn/%3CCAE2DaSQQMb8XVfsn2NSsXQO+-0m2t4U2GD7nYD3GBUO4GPeTxQ%40mail.gmail.com%3E Whole auto sprite flipping
* make a folder of examples
* include uxn5 in the web tutorial so that code can be run from there
=> https://git.sr.ht/~rabbits/uxn5 uxn5
* format and update {uxn running}
## traducción:

View File

@ -26,7 +26,7 @@ these are revised versions of the tutorials below :)
# external resources
=> https://metasyn.github.io/learn-uxn/ learn-uxn by metasyn
=> https://metasyn.srht.site/learn-uxn/ learn-uxn by metasyn
=> https://wiki.xxiivv.com/site/uxn.html uxn technical documentation
=> https://nchrs.xyz/site/uxn_notes.html uxn illustrated notes
=> https://wiki.xxiivv.com/site/uxntal_reference.html the uxntal opcode manual

View File

@ -90,7 +90,7 @@ in this appendix we introduce the use of the on-screen debugger available in uxn
# external resources
=> https://metasyn.github.io/learn-uxn/ learn-uxn by metasyn
=> https://metasyn.srht.site/learn-uxn/ learn-uxn by metasyn
=> https://wiki.xxiivv.com/site/uxn.html uxn technical documentation
=> https://nchrs.xyz/site/uxn_notes.html uxn illustrated notes
=> https://wiki.xxiivv.com/site/uxntal_reference.html the uxntal opcode manual

View File

@ -8,29 +8,31 @@ we also jump right in into our first simple programs to demonstrate fundamental
or first of all... what is uxn?
> The Uxn ecosystem is a personal computing playground, created to host small tools and games, programmable in its own unique assembly language.
> The Uxn/Varvara ecosystem is a personal computing stack based on a small virtual machine that lies at the heart of our software, and that allows us to run the same application on a variety of systems.
=> https://100r.co/site/uxn.html 100R - uxn
=> https://100r.co/site/uxn.html 100R uxn
i invite you to read "why create a smol virtual computer" from that 100R site, as well.
for further context, i invite you to read or watch "weathering software winter" from the 100R site, as well.
=> https://100r.co/site/weathering_software_winter.html 100R — Weathering Software Winter
uxn is the core of the varvara virtual computer. it is simple enough to be emulated by many old and new computing platforms, and to be followed by hand.
personally, i see in it the following features:
* built at a human-scale
* built for audiovisual interactive applications
* simple architecture and instruction set (only 32 instructions!)
* offline-first: it works locally and you only need a couple of documentation files to get going
* practice and experimentation ground for computing within limits
* ported already to several years old and modern computing platforms
* built at a human-scale.
* built for audiovisual interactive applications.
* simple architecture and instruction set (only 36 instructions!)
* offline-first: it works locally and you only need a couple of documentation files to get going.
* practice and experimentation ground for computing within limits.
* ported already to several years old and modern computing platforms.
all these concepts sound great to me, and hopefully to you too!
however, i see in it a couple of aspects that may make it seem not too very approachable:
however, i see in it some aspects that may make it seem not too very approachable:
* it is programmed in an assembly language, uxntal
* it uses the {postfix} notation (aka reverse polish notation) and it is inspired by forth machines
* it is programmed in an assembly language, uxntal.
* it uses the {postfix} notation (aka reverse polish notation) and it is inspired by forth machines.
the idea of this tutorial is to explore these two aspects and reveal how they play along to give uxn its power with relatively little complexity.
@ -46,7 +48,7 @@ this implies that it is primarily based on interactions with a "push down stack"
## postfix addition
in postfix notation, the addition of two numbers would be written in the following form:
in postfix notation, the addition of two numbers, 1 and 48, would be written in the following form:
``` 1 48 +
1 48 +
@ -84,7 +86,7 @@ we can also write it in many other ways, for example:
48 3 5 + 2 / +
```
make sure these expressions work and are equivalent! you just have to follow these rules, reading from left to right:
i invite you to make sure these expressions work and are equivalent! you just have to follow these rules, reading from left to right:
* if it's a number, push it down onto the stack
* if it's an operator, take two elements from the top of the stack, apply the operation, and push the result back onto the stack.
@ -103,6 +105,9 @@ we'll come back to postfix notation and the stack very soon!
one of the perks of programming a computer at a low-level of abstraction, as we will be doing with uxn, is that we have to know and be aware of its internal workings.
for this section of the tutorial, i recommend you to follow along with the great illustrations and notes by Rostiger:
=> https://nchrs.xyz/uxn_notes.html nchrs: uxn notes
## 8-bits and hexadecimal
binary words of 8-bits, also known as bytes, are the basic elements of data encoding and manipulation in uxn.
@ -115,26 +120,26 @@ a byte needs two hexadecimal digits (nibbles) to be expressed, and a short needs
## the uxn cpu
it is said that the uxn cpu is a beet, capable of performing 32 different instructions with three different mode flags.
it is said that the uxn cpu is a beet, capable of performing 36 different instructions with three different mode flags.
each instruction along with its mode flags can be encoded in a single word of 8-bits.
all of these instructions operate with elements in the stack, either to get from it their operands and/or to push down onto it their results.
we'll be covering these instructions slowly over this tutorial.
we'll be covering these instructions very calmly over this tutorial.
## memory
memory in a uxn computer consists in four separate spaces:
* main memory, with 65536 bytes
* i/o memory, with 256 bytes divided in 16 sections (or devices) of 16 bytes each
* working stack, with 256 bytes
* return stack, with 256 bytes
* a main memory, with 65536 bytes.
* i/o memory, with 256 bytes divided in 16 sections (or devices) of 16 bytes each.
* a working stack, with 256 bytes.
* a return stack, with 256 bytes.
each byte in the main memory has an address of 16-bits (2 bytes) in size, while each byte in the i/o memory has an address of 8-bits (1 byte) in size. both of them can be accessed randomly.
the first 256 bytes of the main memory constitute a section called the zero page. this section can be addressed by 8-bits (1 byte), and it is meant for data storage during runtime of the machine.
the first 256 bytes of the main memory constitute a section called the zero-page. this section can be addressed by 8-bits (1 byte), and it is meant for data storage during the runtime of the machine.
there are different instructions for interacting with each of these memory spaces.
@ -152,68 +157,31 @@ once the cpu reads a byte, it decodes it as an instruction and performs it.
the instruction will normally imply a change in the stack(s), and sometimes it may imply a change of the normal flow of the program counter: instead of pointing to the next byte in memory, it can be made to point elsewhere, "jumping" from a place in memory to another.
# installation and toolchain
# usage, installation and toolchain
## desktop bundles
to run varvara, you have several options: running it from your (web)browser, downloading it as a pre-built application, or building it from source yourself.
in order to run varvara locally and off the grid we need to get an appropriate emulator.
## online
the 100R website allows you to download the emulators for major desktop systems; these come bundled with a selection of program in the form of "roms":
you can experiment with all the materials in the tutorial using the learn-uxn site by metasyn:
=> https://metasyn.srht.site/learn-uxn/ learn-uxn by metasyn
=> https://100r.co/site/uxn.html 100R — uxn
for the exercises in day 1, you can alternatively use the uxntal playground that is optimized for working with text-only:
=> https://wiki.xxiivv.com/etc/uxnrepl/index.html Uxntal Playground
depending on your system, you might be able to launch the emulator (uxnemu) with a double-click, or you might need to use a console to navigate to its location and run it:
## desktop bundles, building from source and more
```
$ ./uxnemu
```
that's it! you will be greeted by a screen corresponding to the launcher rom.
from there you will be able to run the other roms by clicking them, pressing Enter, or the Ctrl key. you can go back to the launcher by pressing the F4 key.
### uxnemu controls
these are the controls you can use within uxnemu, regardless of the rom that you are running:
* F1 circles between different zoom levels
* F2 toggles the debugger
* F3 takes a screenshot of the window
* F4 loads the launcher.rom that lets you browse and open roms in the current directory
## building from source
alternatively, you can get the sources from the uxn git repository, where you will also find the building and installation instructions:
=> https://git.sr.ht/~rabbits/uxn ~rabbits/uxn - sourcehut git
## online: learn-uxn site
finally, you can try and experiment with all the materials in the tutorial with the learn-uxn site by metasyn:
=> https://metasyn.github.io/learn-uxn/ learn-uxn by metasyn
## the toolchain
depending on your system, you might see that besides uxnemu there are a couple of other programs, uxnasm and uxncli.
* uxnemu is the full-featured emulator
* uxncli is a console-based emulator
* uxnasm is an uxntal assembler
during this journey we will be writing our programs in uxntal, the assembly language for uxn machines.
these programs will have to be assembled in order to become roms that we will be able to run with an emulator.
in order to run varvara locally and off the grid we need to get an appropriate emulator. for further instructions in how to run varvara in this way, see {uxn running}.
# uxntal and a very basic hello world
uxntal is the assembly language for uxn machines.
we were talking before about the uxn cpu and the 32 instructions it knows how to perform, each of them encoded as a single 8-bit word (byte).
above, we were talking about the uxn cpu and the 36 instructions it knows how to perform, each of them encoded as a single 8-bit word (byte).
that uxntal is an assembly language implies that there's a one-to-one mapping of a written instruction in the language to a corresponding 8-bit word that the cpu can interpret.
uxntal being an assembly language implies that there's a one-to-one mapping of a written instruction in the language to a corresponding 8-bit word that the cpu can interpret.
for example, the instruction ADD in uxntal is encoded as a single byte with the value 18 in hexadecimal, and corresponds to the following set of actions: take the top two elements from the stack, add them, and push down the result.
for example, the instruction ADD in uxntal is encoded as a single byte with the value 18 in hexadecimal (that's what's called its opcode), and corresponds to the following set of actions: take the top two elements from the stack, add them, and push down the result.
in forth-like systems we can see the following kind of notation to express the operands that an instruction takes from the stack, and the result(s) that it pushes down onto the stack:
@ -231,48 +199,31 @@ SUB ( a b -- a-b )
note that the order of the operands in the subtraction is similar to the order for the division as we discussed above when talking about postfix notation: it is as if we moved the operator from between operands, to the end after the second operand.
also, note that in unxtal, text in between parenthesis is a comment, i.e. it is used for documentation purposes.
## a first program
### using left and the launcher
let's use learn-uxn to write our first program:
uxn and the tools that have been built around it allow us to learn and develop completely within the system.
=> https://metasyn.srht.site/learn-uxn/ learn-uxn by metasyn
for instance, when you run the emulator you will see that you can open a program called left: a text editor!
=> https://100r.co/site/left.html 100R — left
open it, as we will write our first program there!
first of all, rename the file you are working on. you can use the visual menu or press Ctrl + r, then delete the current filename, replace it with hello.tal, and press Enter to confirm.
now write the following program:
press the "new" button or delete the code that you find there, and then, write the following code:
```
( hello.tal )
|0100 LIT 68 LIT 18 DEO
|0100 LIT 68 LIT 18 DEO LIT 0a LIT 18 DEO
```
save it using the visual menu or Ctrl + s, and then go back to the launcher by pressing F4.
press the "assemble" button, and look at the box in the bottom-right corner!
you will see now that the listing in the launcher includes your newly created file, hello.tal!
note that this is a text file only, and it's not a rom yet. however, the launcher makes it very easy to convert it to a rom, or more precisely, to assemble it!
just click the hello.tal, or use the arrow keys to reach it and then press Enter or Ctrl: if everything went alright you will see that a hello.tal.rom file appears!
additionally, note that the accompanying console will print something like:
scroll up within that box to see several messages, some of them tagged with [web], others with [asm] and lastly some tagged with [emu].
the [asm] message, corresponding to the output of the assembler that read the code and converted it into a rom, will look something like this:
```
on-reset 0x0000
0x0004 lines of source code.
0x002e bytes of heap used, 0xa34e bytes free.
Assembled output.rom in 10 bytes(0.02% used), 0 labels, 0 macros.
```
if there are errors during assembly, you will see them there.
now that you have hello.tal.rom you can run it from the launcher as any other rom!
when you run it you will see that the screen will be cleared and that the console will show the output of our program:
then, the first [emu] message should read something like this:
```
h
@ -280,40 +231,9 @@ h
interesting, what is happening?
i invite you to try replacing the 68 in the code with, for example, 65.
i invite you to try replacing the 68 in the code with, for example, 65. then, assemble and run the program again.
to do that you'll have to open left again, rename the file to hello.tal so that it opens it, modify the file, save it, return to the launcher, assemble, and then run again!
### using another text editor and uxn tools
alternatively, you can use your favorite text editor and the uxn programs to replicate what we just did from within uxnemu.
you can write hello.tal using your text editor, and then save it along the other files bundled with the emulator:
```
( hello.tal )
|0100 LIT 68 LIT 18 DEO
```
once you have the file, we can assemble and run it from the console:
```
$ ./uxnasm hello.tal hello.rom && ./uxnemu hello.rom
```
a black window will open, and in the console we will see an output that looks like the following:
```
Assembled hello.rom in 5 bytes(0.01% used), 0 labels, 0 macros.
Loaded hello.rom
h
```
as we saw in the previous case, the last 'h' we see is the output of our program.
feel free to use any of these approaches, either working completely from within uxnemu, or using your own tools and the uxn programs.
everything that follows in this journey regarding uxntal is completely agnostic to the tooling you use!
what was the difference in the output now?
## one instruction at a time
@ -321,38 +241,40 @@ we just ran the following program written in uxntal:
```
( hello.tal )
|0100 LIT 68 LIT 18 DEO
|0100 LIT 68 LIT 18 DEO LIT 0a LIT 18 DEO
```
now let's analyze it!
the first line is a comment: comments are enclosed between parenthesis and there have to be spaces in between them. similar to other programming languages, comments are ignored by the assembler.
the first line is a comment: comments are enclosed between parenthesis. there have to be spaces in between them. similar to other programming languages, comments are ignored by the assembler.
the second line has several things going on:
* |0100 : you may remember this number from before - this is the initial value of the program counter; the address of the first byte that the cpu reads. we use this notation to indicate that whatever is written afterwards, will be written in memory starting at this address.
* LIT : this appears twice; it is an uxn instruction that performs the following actions: it pushes the next byte in memory down onto the stack, and it makes the program counter skip that byte.
* 68 : an hexadecimal number, that corresponds to the ascii code of the character 'h'
* 18 : an hexadecimal number, that corresponds to an i/o address: device 1, port 8.
* DEO : another uxn instruction, that we could define as the following: output the given value (1 byte) into the given device address, both taken from the stack ( value address -- )
* |0100 : you may remember this number from before—this is the initial value of the program counter; the address of the first byte in the main memory that the cpu reads. we use this notation to indicate that whatever is written afterwards, will be written in memory starting at this address.
* LIT : this appears four times; it is an uxntal instruction that performs the following actions: it pushes the next byte in memory down onto the stack, and it makes the program counter skip that byte.
* 68 : an hexadecimal number that corresponds to the ascii code of the character 'h'.
* 18 : an hexadecimal number that corresponds to an i/o address: device 1, port 8.
* 0a : an hexadecimal number that corresponds to the ascii code of a 'newline'.
* DEO : another uxntal instruction, that we could define as the following: output the given value (1 byte) into the given device address, both taken from the stack ( value address -- )
reading the program from left to right, we can see the following behavior:
* the LIT instruction pushes number 68 down onto the stack
* the LIT instruction pushes number 18 down onto the stack
* the DEO instruction takes the top element from the stack (18) and uses it as a device address
* the DEO instruction takes the top element from the stack (68) and uses it as a value to output
* the DEO instruction outputs the value to the device address, leaving the stack empty
* the LIT instruction pushes number 68 down onto the stack.
* the LIT instruction pushes number 18 down onto the stack.
* the DEO instruction takes the top element from the stack (18) and uses it as a device address.
* the DEO instruction takes the top element from the stack (68) and uses it as a value to output.
* the DEO instruction outputs the value to the device address, leaving the stack empty.
* then, the same behavior is repeated, but with the number 0a instead of 68.
and what is the i/o device with address 18?
we're talking about a device address, 18, but what does it mean?
looking at the devices table from the varvara reference, we can see that the device with address 1 in the high nibble is the console (standard input and output), and that the column with address 8 in the low nibble corresponds to the "write" port.
=> https://wiki.xxiivv.com/site/varvara.html varvara
=> https://wiki.xxiivv.com/site/varvara.html#console varvara: console device
so, device address 18 corresponds to "console write", or standard output.
our program is sending the hexadecimal value 68 (character 'h') to standard output!
our program is sending the hexadecimal values 68 (character 'h') and 0a (newline) to standard output!
you can see the hexadecimal values of the ascii characters in the following table:
@ -360,43 +282,34 @@ you can see the hexadecimal values of the ascii characters in the following tabl
### raw numbers
note that the raw numbers that we wrote, 0100, 18 and 68, are written in hexadecimal using either 4 digits corresponding to two bytes, or 2 digits corresponding to one byte.
note that the raw numbers that we wrote, 0100, 18, 68 and 0a, are written in hexadecimal using either 4 digits corresponding to two bytes, or 2 digits corresponding to one byte.
in uxntal we can only write numbers that are 2 or 4 hexadecimal digits long. if, for example, we were only interested in writing a single hexadecimal digit, we would have to include a 0 at its left.
in uxntal we can only write numbers that are 2 or 4 hexadecimal digits (nibbles) long. if, for example, we were only interested in writing a single hexadecimal digit, we would have to include a 0 at its left.
## assembled rom
when we assembled our program, we saw that it was 5 bytes in size.
when we assembled our program, we saw that it was 10 bytes in size.
we can confirm it using the wc (word count) program:
if we looked at the numerical contents of the rom, we would see something like this:
```
$ wc -c hello.rom
5 hello.rom
80 68 80 18 17 80 0a 80 18 17
```
for the curious (like you!), we could use a tool like hexdump to see its contents:
80 is the "opcode" corresponding to LIT, and 17 is the opcode corresponding to DEO. and there you can see our 68, 18 and 0a!
```
$ hexdump -C hello.rom
00000000 80 68 80 18 17 |.h...|
00000005
```
80 is the "opcode" corresponding to LIT, and 17 is the opcode corresponding to DEO. and there they are our 68 and 18!
so, effectively, our assembled program matches one-to-one the instructions we just wrote!
so, our assembled program matches one-to-one the instructions we just wrote!
actually, we could have written our program using these hexadecimal numbers, i.e. the machine code, and it would have worked the same way:
```
( hello.tal )
|0100 80 68 80 18 17 ( LIT 68 LIT 18 DEO )
|0100 80 68 80 18 17 80 0a 80 18 17 ( LIT 68 LIT 18 DEO LIT 0a LIT 18 DEO )
```
maybe not the most practical way of programming, but indeed a fun one :)
maybe it's not the most practical way of programming, but indeed it's a fun and beautiful one :)
you can find the opcodes of all 32 instructions in the uxntal reference
you can find the opcodes of all 36 instructions in the uxntal reference
=> https://wiki.xxiivv.com/site/uxntal.html XXIIVV - uxntal
@ -422,13 +335,14 @@ we'll look now at some features of uxntal that make writing and reading code a m
# runes, labels, macros
runes are special characters that indicate to uxnasm some pre-processing to do when assembling our programs.
runes are special characters that indicate to the assembler some pre-processing to do when assembling our programs.
## absolute pad rune
we already saw the first of them: | defines an "absolute pad", i.e. the address where the next written items will be located in memory.
we already saw the first of them: | defines an "absolute pad", i.e. the address where the next written items will be located in the main memory.
if the address is 1-byte long, it is assumed to be either an address of the i/o memory space or the zero-page.
if the address is 1-byte long, it is assumed to be an address of the i/o memory space or of the zero page.
if the address is 2-bytes long, it is assumed to be an address for the main memory.
## literal hex rune
@ -441,14 +355,14 @@ using this rune, we could re-write our first program as:
```
( hello.tal )
|0100 #68 #18 DEO
|0100 #68 #18 DEO #0a #18 DEO
```
the following would have the same behavior as the program above, but using one less byte (in the next day of the tutorial we'll see why)
the following would have the same behavior as the program above, but using two bytes less (in the next day of the tutorial we'll see why)
```
( hello.tal )
|0100 #6818 DEO
|0100 #6818 DEO #0a18 DEO
```
note that you can only use this rune to write the contents of either one or two bytes, i.e. two or four nibbles.
@ -461,7 +375,7 @@ if we just want to have a specific number in the main memory, without pushing it
this is the raw character or string rune: "
uxnasm reads the ascii character after the rune, and decodes its numerical value.
the assembler reads the ascii character after the rune, and decodes its numerical value.
using this rune, our "hello program" would look like the following:
@ -481,37 +395,35 @@ that's why we need to include a LIT instruction.
## runes for labels
even though right now we know that #18 corresponds to pushing the console write device port down onto the stack, for readability and future-proofing of our code it is a good practice to assign a set of labels that would correspond to that device and port.
even though right now we know that #18 corresponds to pushing the address (18) of the console write device port down onto the stack, for readability and future-proofing of our code it is a good practice to assign a set of labels that would correspond to that device and port.
the rune @ allows us to define labels, and the rune & allows us to define sub-labels.
for example, for the console device, the way you would see this written in uxntal programs for the varvara computer is the following:
```
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
|10 @Console &vector $2 &read $1 &pad $5 &write $1 &error $1
```
we can see an absolute pad to address 10, that assigns the following items to that address. because the address consists of one byte only, uxnasm assumes it is for the i/o memory space or the zero page.
here, we can see an absolute pad to address 10 (the console device), that assigns the following items to that address. because the address consists of one byte only, once we use the DEO instructions, uxn understands it's referring to the i/o memory space,.
then we see a label @Console: this label is assigned to address 10.
the square brackets are ignored, but included for readability.
next we have several sub-labels, indicated by the & rune, and relative pads, indicated by the $ rune. how do we read and interpret them?
next we have several sub-labels, indicated by the & rune, and relative pads, indicated by the $ rune. how do we read/interpret them?
* sublabel &vector has the same address as its parent label @Console: 10
* $2 skips two bytes (we could read this as &vector being an address to a 2-bytes long word)
* sublabel &read has the address 12
* $1 skips one byte (&read would be an address for a 1-byte long word)
* sublabel &pad has the address 13
* $5 skips the remaining bytes of the first group of 8 bytes in the device: these bytes correspond to the "inputs"
* sublabel &write has the address 18 (the one we knew already!)
* $1 skips one byte (&write would be an address for a 1-byte long word)
* sublabel &error has the address 19
* sublabel &vector has the same address as its parent label @Console: 10.
* $2 skips two bytes (we could read this as &vector being an address to a 2-bytes long word).
* sublabel &read has the address 12.
* $1 skips one byte (&read would be an address for a 1-byte long word).
* sublabel &pad has the address 13.
* $5 skips the remaining bytes of the first group of 8 bytes in the device: these bytes correspond to the "inputs".
* sublabel &write has the address 18 (the one we knew already!).
* $1 skips one byte (&write would be an address for a 1-byte long word).
* sublabel &error has the address 19.
none of this would be translated to machine code, but aids us in writing uxntal code.
the rune for referring to literal address in the zero page or i/o address space, is . (dot), and a / (slash) allows us to refer to one of its sublabels.
the rune for referring to literal addressess in the zero page or i/o address space, is . (dot), and a / (slash) allows us to refer to one of its sublabels.
remember: as a "literal address" rune it will add a LIT instruction before the corresponding address :)
@ -521,7 +433,7 @@ we could re-write our "hello program" as follows:
( hello.tal )
( devices )
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
|10 @Console &vector $2 &read $1 &pad $5 &write $1 &error $1
( main program )
|0100 LIT "h .Console/write DEO
@ -543,7 +455,7 @@ during assembly, these macros are recursively replaced by the contents in their
for example, we can see that the following piece of code is repeated many times in our program:
```
.Console/write DEO ( equivalent to #18 DEO, or LIT 18 DEO )
.Console/write DEO
```
we could define a macro called EMIT that will take from the stack a byte corresponding to a character, and print it to standard output.
@ -571,6 +483,10 @@ we can call macros inside macros, for example:
%NL { #0a EMIT } ( -- )
```
note that macros are a helpful way of grouping and reusing code, especially when beginning to learn uxntal. for more advanced uses, macros are replaced by other strategies.
for that reason, some uxntal assemblers like the one in Uxntal Playground, don't allow their use.
# a more idiomatic hello world
using all these macros and runes, our program could end up looking like the following:
@ -578,7 +494,7 @@ using all these macros and runes, our program could end up looking like the foll
```
( hello.tal )
( devices )
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
|10 @Console &vector $2 &read $1 &pad $5 &write $1 &error $1
( macros )
( print a character to standard output )
@ -615,7 +531,9 @@ EMIT EMIT EMIT EMIT EMIT
if you look at the ascii table, you'll see that the hexadecimal ascii code 30 corresponds to the digit 0, 31 to the digit 1, and so on until 39 that corresponds to digit 9.
define a PRINT-DIGIT macro that takes a number (from 0 to 9) from the stack, and prints its corresponding digit to standard output.
=> https://wiki.xxiivv.com/site/ascii.html ascii table
define a PRINT-DIGIT macro that takes a number (from 00 to 09) from the stack, and prints its corresponding digit to standard output.
```
%PRINT-DIGIT { } ( number -- )