Building a Human Resource Machine Compiler - Language Design
Building a Human Resource Machine Compiler - Language Design
This article is part of the Human Resource Machine Compiler series.
Now, we need to determine what’s truly essential for this language. The game’s mechanics are fairly simple, and the processor used is quite basic. We’ll start by focusing on the early levels of the game and gradually expand our language as we progress through the later stages. The language will be effective up to level year 20, which is where I’m currently at, and it doesn’t seem to require much brain to reach this point.
In the game, we’re tasked with performing various operations, so it appears we’ll be designing an imperative language. At the moment, the game can only handle one type of data—integers. To support logical operations, we’re also defining a boolean type. We’ll need to implement arithmetic and logical operations, but bitwise operations are not necessary for now. For these operations, we’ll follow the precedence and associativity rules as in C. Whether or not we evaluate expressions in short-circuit style can be decided later.
We introduce the basic operations:
- Arithmetic
+
, add-
, subtract*
, multiply/
, integer division%
, modulus++
, increment--
, decrement
- Logical
&&
, and||
, or>
, greater than>=
, greater than or equal<
, less than<=
, less than or equal==
, equal!=
, not equal!
, not
The operator precedence and associativity is define below.
Precedence | Operator | Description | Associativity |
---|---|---|---|
12 | a() | Function call | Left-to-right → |
11 | floor[] | Floor access | Left-to-right → |
10 | ++a --a | Prefix increment and decrement | Right-to-left ← |
9 | +a -a | Unary plus and minus | Right-to-left ← |
8 | ! | Logical NOT | Right-to-left ← |
7 | a*b a/b a%b | Multiplication, division, and remainder | Left-to-right → |
6 | a+b a-b | Addition and subtraction | Left-to-right → |
5 | < <= > >= | Relational operators | Left-to-right → |
4 | == != | Equality operators | Left-to-right → |
3 | && | Logical AND | Left-to-right → |
2 | || | Logical OR | Left-to-right → |
1 | = | Direct assignment | Right-to-left ← |
Variables can be defined with scopes similar to those in other languages. We support global, function, and block lifetimes. In this language, variable shadowing is not allowed, meaning you cannot redefine a variable within a block if it’s already defined in an outer scope. We’re also skipping static
variables as they don’t seem necessary.
The language doesn’t have a strong concept of types—all we’re dealing with are integers. While we might use boolean values, they can easily be represented as integers. As a syntactic sugar, we define true
and false
, which will internally be treated as 1 and 0. Similar to C, in logical operations, any non-zero value is considered true
, while zero is treated as false
.
Since the game lacks a stack or heap, our language won’t have them either. Instead, we have a series of memory cells (referred to as "floor"). Users can access specific floor values directly, while the language will manage the rest of the floor boxes automatically. To facilitate compilation and optimization, we introduce two floor directives: init floor[IDX] = VAL
and init floor_max = VAL
. The first instructs the compiler that certain floor boxes have preset values, while the second specifies the maximum number of slots available on the floor.
Given that the game mimics a basic processor, we're designing the language to be close to very low-level but still capable of handling common operations and logic. Variable names are statically bound, and user-defined types are not allowed. Basic selection and loop constructs are required in the game, so we’ll include them in the language. We define a while
loop (while (CONDITION) {}
), a for
loop (for (LOOP_VARIABLE_DECL, LOOP_CONDITION, LOOP_ADVANCEMENT) {}
), and an if
statement (if (CONDITION) {} else {}
). There are also continue
and break
defined for loops.
Lastly, the subprogram. While the game doesn’t support subprograms natively, we might need them occasionally. We define simplified subprogram constructs that allow only one parameter, making it easier to adapt to the game’s processor. If a subprogram has a return value, it should be a function
. Otherwise, use sub
(just like in BASIC 😄). We also define two special subprograms, inbox()
and outbox(val)
, which serve the same purpose as in the game. The calling and returning mechanics are purely implementation defined.
One last thing—modules. We might want to design common procedures across levels for reuse. We introduce import MODULE_NAME
. One caveat is that floor initialization instructions may only appear once in the entire program, but this rule can be enforced during compilation.
This will be our minimalist Human Resource Machine LazyCoder Language
. Here’s how a program in this language might look:
import lang_mod;
// only single line comments allowed
init floor[1] = 1; // init floor[id] sets the initial value for a box on the floor
init floor[0] = 0;
init floor_max = 10;
// A function is defined, which takes an argument.
// Only one or zero argument is allowed.
function countdown(a) {
for (let i = a, i >= 0, --i) {
if (a % 2 == 0) {
continue;
}
outbox(i);
}
return a;
}
// sub marks a no-return function
sub no_return_func() {
// and it does nothing
}
// the program starts here
sub start() {
// a loop construct
// int(true) = 1, int(false) = 0, boolean and int are basically the same thing
// bool(1) = true, bool(0) = false, bool(anything except 0) = true
while (true) {
// inbox and outbox
let a = inbox();
let b = inbox();
if (a > b) {
outbox(a);
} else {
outbox(b);
}
let c = a * b;
let num = countdown(a);
floor[2] = num;
floor[3] = floor[b] + c;
outbox(c);
}
}