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():
#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:
#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:
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:
$ 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:
#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:
UNIT_OP_JUMP, which unconditionally jumps to a label.UNIT_OP_JUMP_IF_TRUE, which jumps to a label if “true” is on top of the stack (more on this in a moment).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:
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:
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.
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:
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.