Building a guessing game#

Goal#

In the previous section, we compiled a procedure that returns 0. Now let’s turn it into an actual guessing game. We’ll build it up piece by piece, testing each step along the way.

We want the final game to look something like this:

Welcome to a guessing game!
The number is between 1 and 100.
Enter a guess (1-100): 50
Higher
Enter a guess (1-100): 75
Lower
Enter a guess (1-100): 62
Higher
Enter a guess (1-100): 68
You win!

Hardcoded values#

Before dealing with user input or random numbers, let’s hardcode everything and make sure the arithmetic works. Our procedure will store a guess of 50, then print it.

UNIT provides local variables for storing values between instructions. Each local is identified by an integer index. You store a value with UNIT_OP_STORE_LOCAL and retrieve it with UNIT_OP_LOAD_LOCAL:

// Local 0 = answer, Local 1 = guess

// answer (index 0) = 42
ADDOP_INT(UNIT_OP_LOAD_INTEGER, 42);
ADDOP_INT(UNIT_OP_STORE_LOCAL, 0);

// guess (index 1) = 50
ADDOP_INT(UNIT_OP_LOAD_INTEGER, 50);
ADDOP_INT(UNIT_OP_STORE_LOCAL, 1);

Hint

If the idea of using indices as variable names doesn’t sit right with you, think of it like storing a value at an index in an infinitely large array:

// variables[0] = 42
ADDOP_INT(UNIT_OP_LOAD_INTEGER, 42);
ADDOP_INT(UNIT_OP_STORE_LOCAL, 0);

STORE_LOCAL pops the top of the stack into the local at the given index. LOAD_LOCAL pushes the value of a local back onto the stack.

You can use any index you like – UNIT allocates storage automatically. In fact, the local variable ID isn’t really an index at all! The following is perfectly valid and does not use exorbitant amounts of memory:

ADDOP_INT(UNIT_OP_LOAD_INTEGER, 42);
ADDOP_INT(UNIT_OP_STORE_LOCAL, 100000000);

UNIT maintains a mapping of your local variable IDs to real ones used during translation, so it’s perfectly fine to do this.

However, keeping track of which index means what gets tedious quickly. UNIT provides UNIT_Procedure_CreateLocal() to manage this for you. It assigns an index internally and gives you a UNIT_Local handle that you pass to UNIT_Procedure_AddStoreName() and UNIT_Procedure_AddLoadName():

C#
#define NEW_LOCAL(name)                                                          \
    UNIT_Local name;                                                             \
    if (UNIT_FAILED(UNIT_Procedure_CreateLocal(&procedure, #name, &name))) {     \
        goto error;                                                              \
    }

#define STORE_NAME(name)                                                         \
       if (UNIT_FAILED(UNIT_Procedure_AddStoreName(&procedure, name))) {         \
        goto error;                                                              \
    }

NEW_LOCAL(answer);
NEW_LOCAL(guess);

// answer = 42
ADDOP_INT(UNIT_OP_LOAD_INTEGER, 42);
STORE_NAME(answer);

// guess = 50
ADDOP_INT(UNIT_OP_LOAD_INTEGER, 50);
STORE_NAME(guess);

This is equivalent to the raw index version, but the names make the code self-documenting and show up in debug output from UNIT_Procedure_PrintInstructions().

We’ll use named locals for the rest of the tutorial.

Now let’s print the guess to make sure it works. We need to call printf, which takes a format string and the value to print. Use UNIT_Procedure_AddStringLoad() for the format string and UNIT_Procedure_AddCallName() for the call:

C#
 #define LOAD_STRING(value)                                                  \
     if (UNIT_FAILED(UNIT_Procedure_AddStringLoad(&procedure, value))) {     \
         goto error;                                                         \
     }

 #define LOAD_NAME(name)                                                         \
     if (UNIT_FAILED(UNIT_Procedure_AddLoadName(&procedure, name))) {            \
        goto error;                                                              \
     }

 #define CALL_NAME(name, nargs)                                                  \
     if (UNIT_FAILED(UNIT_Procedure_AddCallName(&procedure, name, nargs))) {     \
         goto error;                                                             \
     }

 // printf("You guessed: %d\n", guess)
 LOAD_STRING("You guessed: %d\n");
 LOAD_NAME(guess);
 CALL_NAME("printf", 2);
 ADDOP(UNIT_OP_POP); // discard printf's return value

 ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
 ADDOP(UNIT_OP_RETURN_VALUE);

The UNIT_OP_POP after the call discards printf’s return value (the number of characters printed), because we don’t need it.

Here’s the full program again:

main.c#
 1 #include <unit/unit.h>
 2 #include <stdio.h>
 3
 4 int main(void)
 5 {
 6     UNIT_Context context;
 7     if (UNIT_FAILED(UNIT_Context_Init(&context))) {
 8         fprintf(stderr, "failed to initialize context\n");
 9         return 1;
10     }
11
12     UNIT_Procedure procedure;
13     if (UNIT_FAILED(UNIT_Procedure_Init(&procedure, &context, "main"))) {
14         UNIT_PrintError(&context, stderr);
15         UNIT_Context_Clear(&context);
16         return 1;
17     }
18
19     #define ADDOP_INT(op, value)                                                \
20         if (UNIT_FAILED(UNIT_Procedure_AddOperation(&procedure, op, value))) {  \
21             goto error;                                                         \
22         }
23
24     #define ADDOP(op) ADDOP_INT(op, 0)
25
26     #define NEW_LOCAL(name)                                                          \
27         UNIT_Local name;                                                             \
28         if (UNIT_FAILED(UNIT_Procedure_CreateLocal(&procedure, #name, &name))) {     \
29             goto error;                                                              \
30         }
31
32     #define STORE_NAME(name)                                                         \
33             if (UNIT_FAILED(UNIT_Procedure_AddStoreName(&procedure, name))) {        \
34             goto error;                                                              \
35         }
36
37     #define LOAD_STRING(value)                                                  \
38         if (UNIT_FAILED(UNIT_Procedure_AddStringLoad(&procedure, value))) {     \
39             goto error;                                                         \
40         }
41
42     #define LOAD_NAME(name)                                                         \
43         if (UNIT_FAILED(UNIT_Procedure_AddLoadName(&procedure, name))) {            \
44             goto error;                                                             \
45         }
46
47     #define CALL_NAME(name, nargs)                                                  \
48         if (UNIT_FAILED(UNIT_Procedure_AddCallName(&procedure, name, nargs))) {     \
49             goto error;                                                             \
50         }
51
52     NEW_LOCAL(answer);
53     NEW_LOCAL(guess);
54
55     // answer = 42
56     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 42);
57     STORE_NAME(answer);
58
59     // guess = 50
60     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 50);
61     STORE_NAME(guess);
62
63     // printf("You guessed: %d\n", guess)
64     LOAD_STRING("You guessed: %d\n");
65     LOAD_NAME(guess);
66     CALL_NAME("printf", 2);
67     ADDOP(UNIT_OP_POP);
68
69     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
70     ADDOP(UNIT_OP_RETURN_VALUE);
71
72     if (UNIT_FAILED(UNIT_Procedure_Optimize(&procedure))) {
73         goto error;
74     }
75
76     UNIT_CompiledProcedure *compiled = UNIT_Compile(&procedure, UNIT_HOST_PLATFORM);
77     if (compiled == NULL) {
78         goto error;
79     }
80
81     if (UNIT_FAILED(UNIT_CompiledProcedure_WriteObjectFile(compiled, "output.o",
82                                                            UNIT_FORMAT_ELF))) {
83         UNIT_CompiledProcedure_Free(compiled);
84         goto error;
85     }
86
87     printf("Wrote output.o\n");
88
89     UNIT_CompiledProcedure_Free(compiled);
90     UNIT_Procedure_Clear(&procedure);
91     UNIT_Context_Clear(&context);
92     return 0;
93 error:
94     UNIT_PrintError(&context, stderr);
95     UNIT_Procedure_Clear(&procedure);
96     UNIT_Context_Clear(&context);
97     return 1;
98 }

Build, compile the object file, link, and run:

bash#
$ gcc main.c -lunit -o guessing_game
$ ./guessing_game
$ gcc output.o -o output -lc
$ ./output
You guessed: 50

Yay, it works!

Comparing the guess#

Now let’s compare the guess to the answer and print “Higher”, “Lower”, or “Correct”. This requires jump labels and conditional branches.

The logic is:

if guess == answer:
    print "Correct!"
if guess > answer:
    print "Lower"
otherwise:
    print "Higher"

However, UNIT does not have “if” clauses like normal programming languages do. Instead, UNIT has jumps. You can think of this like the goto statement in C.

To create a point that we can jump to, we need to set a label. For our purposes, we’re going to create three labels – one for each branch, plus one for the end:

C#
 #define NEW_JUMP_LABEL(name)                                                    \
     UNIT_JumpLabel *name = UNIT_Procedure_CreateJumpLabel(&procedure, #name);   \
     if (name == NULL) {                                                         \
         goto error;                                                             \
     }

 NEW_JUMP_LABEL(correct);
 NEW_JUMP_LABEL(lower);
 NEW_JUMP_LABEL(end);

A UNIT_JumpLabel * is owned by the procedure – we are not in charge of freeing it (it will be destroyed upon calling UNIT_Procedure_Clear()).

To jump to a label, UNIT provides three instructions:

  1. UNIT_OP_JUMP, which unconditionally jumps to a label.

  2. UNIT_OP_JUMP_IF_TRUE, which jumps to a label if “true” is on top of the stack (more on this in a moment).

  3. UNIT_OP_JUMP_IF_FALSE, which jumps to a label if “false” is on top of the stack.

Now, we haven’t talked about pushing “true” or “false” yet. UNIT has a number of special comparison instructions that do this:

  1. UNIT_OP_COMPARE_EQUAL (==)

  2. UNIT_OP_COMPARE_NOT_EQUAL (!=)

  3. UNIT_OP_COMPARE_GREATER (>)

  4. UNIT_OP_COMPARE_GREATER_EQUAL (>=)

  5. UNIT_OP_COMPARE_LESS (<)

  6. UNIT_OP_COMPARE_LESS_EQUAL (<=)

Each of these instructions will pop two items off of the stack and compare them. For our purposes of jumping to our “correct” label, we want to compare guess and answer as equal, and then jump if the result is true.

#define LOAD_NAME(name)                                                  \
    if (UNIT_FAILED(UNIT_Procedure_AddLoadName(&procedure, name))) {     \
        goto error;                                                      \
    }

 #define ADDOP_JUMP(op, label)                                           \
     if (UNIT_FAILED(UNIT_Procedure_AddJump(&procedure, op, label))) {   \
         goto error;                                                     \
     }

// if guess == answer: goto correct
LOAD_NAME(guess);
LOAD_NAME(answer);
ADDOP(UNIT_OP_COMPARE_EQUAL);
ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, correct);

Great, but this doesn’t work so far, because we haven’t told UNIT where the “correct” label is. It has nowhere to jump to!

To set a label, we have to call UNIT_Procedure_UseLabel(). A label can only be “used” (or “placed”) once. When we jump to the label, execution will continue from the next instruction after it. So, let’s adjust our code to add labels where need them. While we’re here, let’s also add the other comparisons necessary for our guessing game:

#define USE_LABEL(label)                                             \
    if (UNIT_FAILED(UNIT_Procedure_UseLabel(&procedure, label))) {   \
        goto error;                                                  \
    }

// if guess == answer: goto correct
LOAD_NAME(guess);
LOAD_NAME(answer);
ADDOP(UNIT_OP_COMPARE_EQUAL);
ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, correct);

// if guess > answer: goto lower
LOAD_NAME(guess);
LOAD_NAME(answer);
ADDOP(UNIT_OP_COMPARE_GREATER);
ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, lower);

// Print out "Higher" here

ADDOP_JUMP(UNIT_OP_JUMP, end); // Skip past the "lower" block
USE_LABEL(lower);

// Print out "Lower" here

ADDOP_JUMP(UNIT_OP_JUMP, end); // Skip past the "correct" block
USE_LABEL(correct);

// Correct, end game.

USE_LABEL(end);

Great, now let’s add the printf calls we want there. We’ll follow the same practice as before:

// if guess == answer: goto correct
LOAD_NAME(guess);
LOAD_NAME(answer);
ADDOP(UNIT_OP_COMPARE_EQUAL);
ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, correct);

// if guess > answer: goto lower
LOAD_NAME(guess);
LOAD_NAME(answer);
ADDOP(UNIT_OP_COMPARE_GREATER);
ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, lower);

// printf("Higher\n!")
LOAD_STRING("Higher\n");
CALL_NAME("printf", 1);
ADDOP(UNIT_OP_POP);

ADDOP_JUMP(UNIT_OP_JUMP, end); // Skip past the "lower" block
USE_LABEL(lower);

// printf("Lower\n!")
LOAD_STRING("Lower\n");
CALL_NAME("printf", 1);
ADDOP(UNIT_OP_POP);

ADDOP_JUMP(UNIT_OP_JUMP, end); // Skip past the "correct" block
USE_LABEL(correct);

// printf("Correct!\n")
LOAD_STRING("Correct!\n");
CALL_NAME("printf", 1);
ADDOP(UNIT_OP_POP);

// return 0
ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
ADDOP(UNIT_OP_RETURN_VALUE);

USE_LABEL(end);

Now, if we compile and run:

./guessing_game && gcc output.o -o output -lc && ./output
Lower

Try changing the hardcoded guess to 42 and verify it prints “Correct!”, or to 30 and verify that it prints “Higher”.

Random numbers and user input#

Now let’s replace the hardcoded values with a random number and user input.

For the answer, we call rand() and compute (rand() % 100) + 1 to get a number between 1 and 100. We also need to call srand(time(NULL)) to seed the random number generator.

Replace the hardcoded answer with:

// srand(time(NULL)) -- NULL is just 0
ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
CALL_NAME("time", 1);
// [time_result]

CALL_NAME("srand", 1);
ADDOP(UNIT_OP_POP);

// answer = (rand() % 100) + 1
CALL_NAME("rand", 0);
ADDOP_INT(UNIT_OP_LOAD_INTEGER, 100);
ADDOP(UNIT_OP_MODULO);
// [rand() % 100]

ADDOP_INT(UNIT_OP_LOAD_INTEGER, 1);
ADDOP(UNIT_OP_ADD);
// [(rand() % 100) + 1]

STORE_NAME(answer);

For the guess, we use scanf to read an integer from the user. scanf takes a format string and a pointer to the variable to store the result in. UNIT provides UNIT_OP_ADDRESS_OF to get the address of a local variable.

Note

UNIT_OP_ADDRESS_OF takes a raw local index, not a UNIT_Local handle. Since we used UNIT_Procedure_CreateLocal(), we can access the index through the id field. This is the one case where you need the raw index.

Replace the hardcoded guess with:

// printf("Enter your guess (1-100): ")
LOAD_STRING("Enter your guess (1-100):");
CALL_NAME("printf", 1);
ADDOP(UNIT_OP_POP);

// scanf("%d", &guess)
LOAD_STRING("%d");
ADDOP_INT(UNIT_OP_ADDRESS_OF, guess.id);
CALL_NAME("scanf", 2);
ADDOP(UNIT_OP_POP);

UNIT_OP_ADDRESS_OF pushes the memory address of the local variable onto the stack. scanf writes the parsed integer directly to that address. After the call, loading guess gives you whatever the user typed.

Here’s the full program again:

main.c#
  1 #include <unit/unit.h>
  2 #include <stdio.h>
  3
  4 int main(void)
  5 {
  6     UNIT_Context context;
  7     if (UNIT_FAILED(UNIT_Context_Init(&context))) {
  8         fprintf(stderr, "failed to initialize context\n");
  9         return 1;
 10     }
 11
 12     UNIT_Procedure procedure;
 13     if (UNIT_FAILED(UNIT_Procedure_Init(&procedure, &context, "main"))) {
 14         UNIT_PrintError(&context, stderr);
 15         UNIT_Context_Clear(&context);
 16         return 1;
 17     }
 18
 19     #define ADDOP_INT(op, value)                                                \
 20         if (UNIT_FAILED(UNIT_Procedure_AddOperation(&procedure, op, value))) {  \
 21             goto error;                                                         \
 22         }
 23
 24     #define ADDOP(op) ADDOP_INT(op, 0)
 25
 26     #define NEW_LOCAL(name)                                                          \
 27         UNIT_Local name;                                                             \
 28         if (UNIT_FAILED(UNIT_Procedure_CreateLocal(&procedure, #name, &name))) {     \
 29             goto error;                                                              \
 30         }
 31
 32     #define STORE_NAME(name)                                                         \
 33             if (UNIT_FAILED(UNIT_Procedure_AddStoreName(&procedure, name))) {        \
 34             goto error;                                                              \
 35         }
 36
 37     #define LOAD_STRING(value)                                                  \
 38         if (UNIT_FAILED(UNIT_Procedure_AddStringLoad(&procedure, value))) {     \
 39             goto error;                                                         \
 40         }
 41
 42     #define LOAD_NAME(name)                                                         \
 43         if (UNIT_FAILED(UNIT_Procedure_AddLoadName(&procedure, name))) {            \
 44             goto error;                                                             \
 45         }
 46
 47     #define CALL_NAME(name, nargs)                                                  \
 48         if (UNIT_FAILED(UNIT_Procedure_AddCallName(&procedure, name, nargs))) {     \
 49             goto error;                                                             \
 50         }
 51
 52     #define NEW_JUMP_LABEL(name)                                                    \
 53         UNIT_JumpLabel *name = UNIT_Procedure_CreateJumpLabel(&procedure, #name);   \
 54         if (name == NULL) {                                                         \
 55             goto error;                                                             \
 56         }
 57
 58     #define ADDOP_JUMP(op, label)                                           \
 59         if (UNIT_FAILED(UNIT_Procedure_AddJump(&procedure, op, label))) {   \
 60             goto error;                                                     \
 61         }
 62
 63     #define USE_LABEL(label)                                             \
 64         if (UNIT_FAILED(UNIT_Procedure_UseLabel(&procedure, label))) {   \
 65             goto error;                                                  \
 66         }
 67
 68     NEW_JUMP_LABEL(correct);
 69     NEW_JUMP_LABEL(lower);
 70     NEW_JUMP_LABEL(end);
 71
 72     NEW_LOCAL(answer);
 73     NEW_LOCAL(guess);
 74
 75     // srand(time(NULL)) -- NULL is just 0
 76     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
 77     CALL_NAME("time", 1);
 78     // [time_result]
 79
 80     CALL_NAME("srand", 1);
 81     ADDOP(UNIT_OP_POP);
 82
 83     // answer = (rand() % 100) + 1
 84     CALL_NAME("rand", 0);
 85     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 100);
 86     ADDOP(UNIT_OP_MODULO);
 87     // [rand() % 100]
 88
 89     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 1);
 90     ADDOP(UNIT_OP_ADD);
 91     // [(rand() % 100) + 1]
 92
 93     STORE_NAME(answer);
 94
 95     // printf("Enter your guess (1-100): ")
 96     LOAD_STRING("Enter your guess (1-100):");
 97     CALL_NAME("printf", 1);
 98     ADDOP(UNIT_OP_POP);
 99
100     // scanf("%d", &guess)
101     LOAD_STRING("%d");
102     ADDOP_INT(UNIT_OP_ADDRESS_OF, guess.id);
103     CALL_NAME("scanf", 2);
104     ADDOP(UNIT_OP_POP);
105
106     // if guess == answer: goto correct
107     LOAD_NAME(guess);
108     LOAD_NAME(answer);
109     ADDOP(UNIT_OP_COMPARE_EQUAL);
110     ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, correct);
111
112     // if guess > answer: goto lower
113     LOAD_NAME(guess);
114     LOAD_NAME(answer);
115     ADDOP(UNIT_OP_COMPARE_GREATER);
116     ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, lower);
117
118     // printf("Higher\n!")
119     LOAD_STRING("Higher\n");
120     CALL_NAME("printf", 1);
121     ADDOP(UNIT_OP_POP);
122
123     ADDOP_JUMP(UNIT_OP_JUMP, end); // Skip past the "lower" block
124     USE_LABEL(lower);
125
126     // printf("Lower\n!")
127     LOAD_STRING("Lower\n");
128     CALL_NAME("printf", 1);
129     ADDOP(UNIT_OP_POP);
130
131     ADDOP_JUMP(UNIT_OP_JUMP, end); // Skip past the "correct" block
132     USE_LABEL(correct);
133
134     // printf("Correct!\n")
135     LOAD_STRING("Correct!\n");
136     CALL_NAME("printf", 1);
137     ADDOP(UNIT_OP_POP);
138
139     // return 0
140     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
141     ADDOP(RETURN_VALUE);
142
143     USE_LABEL(end);
144
145     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
146     ADDOP(UNIT_OP_RETURN_VALUE);
147
148     if (UNIT_FAILED(UNIT_Procedure_Optimize(&procedure))) {
149         goto error;
150     }
151
152     UNIT_CompiledProcedure *compiled = UNIT_Compile(&procedure, UNIT_HOST_PLATFORM);
153     if (compiled == NULL) {
154         goto error;
155     }
156
157     if (UNIT_FAILED(UNIT_CompiledProcedure_WriteObjectFile(compiled, "output.o",
158                                                            UNIT_FORMAT_ELF))) {
159         UNIT_CompiledProcedure_Free(compiled);
160         goto error;
161     }
162
163     printf("Wrote output.o\n");
164
165     UNIT_CompiledProcedure_Free(compiled);
166     UNIT_Procedure_Clear(&procedure);
167     UNIT_Context_Clear(&context);
168     return 0;
169 error:
170     UNIT_PrintError(&context, stderr);
171     UNIT_Procedure_Clear(&procedure);
172     UNIT_Context_Clear(&context);
173     return 1;
174 }

Build and run:

./guessing_game && gcc output.o -o output -lc && ./output
Enter your guess (1-100): 50
Lower

Try a few values. The answer changes each time you run because of the random seed.

The game loop#

We’re almost there!

The last step is to give the user more than one guess. Let’s add a loop so they can keep guessing until they get it right.

The structure is:

loop:
 if guess == answer:
     print "Correct!"
     return 0
 if guess > answer:
     print "Lower"
 otherwise:
     print "Higher"
 goto loop

We’ll create a loop label and put it before the store to guess, and jump to it from our end label.

main.c#
NEW_JUMP_LABEL(loop);

/* Take input, store to guess ... */

/* Compare the guess with the answer */

USE_LABEL(end);
ADDOP_JUMP(UNIT_OP_JUMP, loop);

See the complete program below for the final working guessing game.

The complete program#

Here is the full guessing game. This is a complete, working program that you can compile and run:

main.c#
  1 #include <unit/unit.h>
  2 #include <stdio.h>
  3
  4 int main(void)
  5 {
  6     UNIT_Context context;
  7     if (UNIT_FAILED(UNIT_Context_Init(&context))) {
  8         fprintf(stderr, "failed to initialize context\n");
  9         return 1;
 10     }
 11
 12     UNIT_Procedure procedure;
 13     if (UNIT_FAILED(UNIT_Procedure_Init(&procedure, &context, "main"))) {
 14         UNIT_PrintError(&context, stderr);
 15         UNIT_Context_Clear(&context);
 16         return 1;
 17     }
 18
 19     #define ADDOP_INT(op, value)                                                \
 20         if (UNIT_FAILED(UNIT_Procedure_AddOperation(&procedure, op, value))) {  \
 21             goto error;                                                         \
 22         }
 23
 24     #define ADDOP(op) ADDOP_INT(op, 0)
 25
 26     #define NEW_LOCAL(name)                                                          \
 27         UNIT_Local name;                                                             \
 28         if (UNIT_FAILED(UNIT_Procedure_CreateLocal(&procedure, #name, &name))) {     \
 29             goto error;                                                              \
 30         }
 31
 32     #define STORE_NAME(name)                                                         \
 33             if (UNIT_FAILED(UNIT_Procedure_AddStoreName(&procedure, name))) {        \
 34             goto error;                                                              \
 35         }
 36
 37     #define LOAD_STRING(value)                                                  \
 38         if (UNIT_FAILED(UNIT_Procedure_AddStringLoad(&procedure, value))) {     \
 39             goto error;                                                         \
 40         }
 41
 42     #define LOAD_NAME(name)                                                         \
 43         if (UNIT_FAILED(UNIT_Procedure_AddLoadName(&procedure, name))) {            \
 44             goto error;                                                             \
 45         }
 46
 47     #define CALL_NAME(name, nargs)                                                  \
 48         if (UNIT_FAILED(UNIT_Procedure_AddCallName(&procedure, name, nargs))) {     \
 49             goto error;                                                             \
 50         }
 51
 52     #define NEW_JUMP_LABEL(name)                                                    \
 53         UNIT_JumpLabel *name = UNIT_Procedure_CreateJumpLabel(&procedure, #name);   \
 54         if (name == NULL) {                                                         \
 55             goto error;                                                             \
 56         }
 57
 58     #define ADDOP_JUMP(op, label)                                           \
 59         if (UNIT_FAILED(UNIT_Procedure_AddJump(&procedure, op, label))) {   \
 60             goto error;                                                     \
 61         }
 62
 63     #define USE_LABEL(label)                                             \
 64         if (UNIT_FAILED(UNIT_Procedure_UseLabel(&procedure, label))) {   \
 65             goto error;                                                  \
 66         }
 67
 68     NEW_JUMP_LABEL(correct);
 69     NEW_JUMP_LABEL(lower);
 70     NEW_JUMP_LABEL(end);
 71     NEW_JUMP_LABEL(loop);
 72
 73     NEW_LOCAL(answer);
 74     NEW_LOCAL(guess);
 75
 76     // srand(time(NULL)) -- NULL is just 0
 77     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
 78     CALL_NAME("time", 1);
 79     // [time_result]
 80
 81     CALL_NAME("srand", 1);
 82     ADDOP(UNIT_OP_POP);
 83
 84     // answer = (rand() % 100) + 1
 85     CALL_NAME("rand", 0);
 86     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 100);
 87     ADDOP(UNIT_OP_MODULO);
 88     // [rand() % 100]
 89
 90     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 1);
 91     ADDOP(UNIT_OP_ADD);
 92     // [(rand() % 100) + 1]
 93
 94     STORE_NAME(answer);
 95
 96     USE_LABEL(loop);
 97
 98     // printf("Enter your guess (1-100): ")
 99     LOAD_STRING("Enter your guess (1-100):");
100     CALL_NAME("printf", 1);
101     ADDOP(UNIT_OP_POP);
102
103     // scanf("%d", &guess)
104     LOAD_STRING("%d");
105     ADDOP_INT(UNIT_OP_ADDRESS_OF, guess.id);
106     CALL_NAME("scanf", 2);
107     ADDOP(UNIT_OP_POP);
108
109     // if guess == answer: goto correct
110     LOAD_NAME(guess);
111     LOAD_NAME(answer);
112     ADDOP(UNIT_OP_COMPARE_EQUAL);
113     ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, correct);
114
115     // if guess > answer: goto lower
116     LOAD_NAME(guess);
117     LOAD_NAME(answer);
118     ADDOP(UNIT_OP_COMPARE_GREATER);
119     ADDOP_JUMP(UNIT_OP_JUMP_IF_TRUE, lower);
120
121     // printf("Higher\n!")
122     LOAD_STRING("Higher\n");
123     CALL_NAME("printf", 1);
124     ADDOP(UNIT_OP_POP);
125
126     ADDOP_JUMP(UNIT_OP_JUMP, end); // Skip past the "lower" block
127     USE_LABEL(lower);
128
129     // printf("Lower\n!")
130     LOAD_STRING("Lower\n");
131     CALL_NAME("printf", 1);
132     ADDOP(UNIT_OP_POP);
133
134     ADDOP_JUMP(UNIT_OP_JUMP, end); // Skip past the "correct" block
135     USE_LABEL(correct);
136
137     // printf("Correct!\n")
138     LOAD_STRING("Correct!\n");
139     CALL_NAME("printf", 1);
140     ADDOP(UNIT_OP_POP);
141
142     // return 0
143     ADDOP_INT(UNIT_OP_LOAD_INTEGER, 0);
144     ADDOP(UNIT_OP_RETURN_VALUE);
145
146     USE_LABEL(end);
147
148     ADDOP_JUMP(UNIT_OP_JUMP, loop);
149
150     if (UNIT_FAILED(UNIT_Procedure_Optimize(&procedure))) {
151         goto error;
152     }
153
154     UNIT_CompiledProcedure *compiled = UNIT_Compile(&procedure, UNIT_HOST_PLATFORM);
155     if (compiled == NULL) {
156         goto error;
157     }
158
159     if (UNIT_FAILED(UNIT_CompiledProcedure_WriteObjectFile(compiled, "output.o",
160                                                            UNIT_FORMAT_ELF))) {
161         UNIT_CompiledProcedure_Free(compiled);
162         goto error;
163     }
164
165     printf("Wrote output.o\n");
166
167     UNIT_CompiledProcedure_Free(compiled);
168     UNIT_Procedure_Clear(&procedure);
169     UNIT_Context_Clear(&context);
170     return 0;
171 error:
172     UNIT_PrintError(&context, stderr);
173     UNIT_Procedure_Clear(&procedure);
174     UNIT_Context_Clear(&context);
175     return 1;
176 }
$ gcc main.c -lunit -o guessing_game
$ ./guessing_game
Wrote output.o
$ gcc output.o -o output -lc
$ ./output
Enter your guess (1-100): 50
Lower
Enter your guess (1-100): 25
Higher
Enter your guess (1-100): 37
Correct!

Next steps#

This covers the majority of UNIT’s instruction set. The remaining instructions (UNIT_OP_READ_BYTES, UNIT_OP_WRITE_BYTES, UNIT_OP_CONVERT) are used for lower-level memory manipulation – see the brainfuck example for a program that uses them extensively.

For the full list of instructions, see the opcode reference.