Variabele input
Arnout van Kempen over rommelen in een digitale wereld.
Ons programma kan nu vrij aardig uitkomsten weergeven, maar de som die wordt berekend is hard gecodeerd, dus voor iedere andere berekening moeten we het programma herschrijven en opnieuw opbouwen tot executable. Dat is misschien maar een kleine moeite, het heeft toch iets raars. We gaan dus wat vervolg stappenzetten. Om te beginnen gaan we input op de commandline toevoegen, en daarbij meteen ook maar verschillende rekenopties toevoegen: optellen, aftrekken, vermenigvuldigen en delen. Om het behapbaar te houden gaan we ons wel beperken tot de basisbewerkingen, op twee decimale integers van maximaal 64 bits. Als we ons programma bijvoorbeeld "calc" noemen, dan zou de volgende aanroep verwerkt moeten kunnen worden:
calc 1+1
En de uitkomst is dan natuurlijk 2, 2, 2, 10. Decimaal, hexadecimaal, octaal en binair immers.
We worden hier enorm geholpen door Linux. Of je nu in C, Rust of ARM64 programmeert, Linux levert bij de start van een programma via de stack pointers op naar alle argumenten die aan het programma zijn meegegeven. Als we die argv[n] noemen, dan is argv[0] de naam van het programma zelf, in dit geval dus calc, argv[1] het eerste argument, in dit geval 1+1 en zo verder, totdat er een argument argv[n] komt met de waarde NULL, waarmee het einde van de reeks wordt aangeduid.
Het ophalen van het eerste argument kan dus vrij simpel als volgt:
ldr x0, [sp, 8] // haal pointer naar argv[1] van de stack
Vervolgens zullen we dat argument moeten ontleden, ook wel parsen genoemd. Laten we dat in een aparte subroutine doen, dus:
bl parse_expression
En ergens anders:
parse_expression:
mov x3, 0 // Beginwaarde operand 1
mov x4, 0 // Beginwaarde operand 2
mov x5, 0 // Operatie (bijv. '+')
mov x6, x0 // Pointer naar string
parse_loop:
ldrb w7, [x6], 1 // Lees één karakter
cmp w7, '+' // Controleer of het een '+'
b.eq found_operator
cmp w7, '-' // Controleer of het een '-'
b.eq found_operator
cmp w7, '*' // Controleer of het een '*'
b.eq found_operator
cmp w7, '/' // Controleer of het een '/'
b.eq found_operator
sub w7, w7, '0' // Zet ASCII-cijfer om naar integer
mul x3, x3, 10 // x3 = x3 * 10
add x3, x3, w7 // Voeg cijfer toe aan x3
b parse_loop
found_operator:
mov x5, w7 // Sla operator op in x5
parse_second_operand:
ldrb w7, [x6], 1 // Lees volgende karakter
cbz w7, done_parsing // Stop als null-terminator
sub w7, w7, '0' // Zet ASCII-cijfer om naar integer
mul x4, x4, 10 // x4 = x4 * 10
add x4, x4, w7 // Voeg cijfer toe aan x4
b parse_second_operand
done_parsing:
ret // Klaar met parsen
Met de gezochte waardes in x3 en x4, kunnen we nu aan het rekenen. In x5 staat de bewerking, dus in het hoofdprogramma kunnen we dat als volgt verwerken:
cmp x5, '+' // Optelling?
b.eq do_add
cmp x5, '-'
b.eq do_sub
cmp x5, '*'
b.eq do_mul
cmp x5, '/'
b.eq do_div
b invalid_operator // Ongeldig teken
do_add:
add x6, x3, x4 // x6 = x3 + x4
b print_result
do_sub:
sub x6, x3, x4 // x6 = x3 - x4
b print_result
do_mul:
mul x6, x3, x4 // x6 = x3 * x4
b print_result
do_div:
udiv x6, x3, x4 // x6 = x3 / x4
b print_result
invalid_operator:
ldr x0, =error_msg // Laad foutmelding
mov x8, 64 // Syscall: write
svc 0
b exit_program
print_result:
mov x5, x6 // Plaats resultaat in x5
// hier komt de code van de vorige keer
exit_program:
// zie code vorige keer
In principe hebben we hiermee een heuse calculator bij elkaar. We moeten deze code en die van de vorige keer nog wel samenvoegen, bekijken of we de logica en het gebruik van registers wat moeten stroomlijnen en foutafhandeling toevoegen. Op dit moment beperkt die foutafhandeling zich tot het ontbreken van een correcte operator, maar er kan nog veel meer mis gaan en dat zullen we moeten afvangen. Dan kan het ook netjes zijn om een wat uitgebreidere tekst toe te voegen over hoe het programma correct te gebruiken.
De code is redelijk leesbaar zo, maar een commando heeft nog wat toelichting nodig:
ldrb w7, [x6], 1 is een samenstelling van ldr dat we al eerder zagen, maar nu wordt 1 byte gelezen, vandaar de b erachter. Maar de opdracht doet nog meer, de ,1 op het eind betekent dat x6 met 1 verhoogd wordt na het inlezen van de byte op adres x6.
Wie mee wil doen met #klooienmetcomputers kan dat doen via GitHub. Maak een account op github.com en zoek naar Abmvk/kmc. Het account Abmvk volgen kan ook. Lezers zijn vrij te gebruiken wat ze willen en om zelf zaken toe te voegen of aan te passen, vragen te stellen of commentaar te leveren.
Gerelateerd

Einde van deze rubriek
Arnout van Kempen besluit met aflevering 112 voorlopig zijn reeks van wekelijkse bijdragen over 'rommelen in een digitale wereld': het zelf leren programmeren, verkennen...

Modulair bouwen
Arnout van Kempen over rommelen in een digitale wereld.

Tools voor debugging
Arnout van Kempen over rommelen in een digitale wereld.

Een echt project!
Arnout van Kempen over rommelen in een digitale wereld.

Een eerste toepassing
Arnout van Kempen over rommelen in een digitale wereld.