about summary refs log tree commit diff stats
path: root/309stream.subx
blob: 524edfd065358dadbd9e371cd314f15ba596367b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# Some unsafe methods not intended to be used directly in SubX, only through
# Mu after proper type-checking.

== code

stream-empty?:  # s: (addr stream _) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    56/push-esi
    # result = false
    b8/copy-to-eax 0/imm32/false
    # esi = s
    8b/-> *(ebp+8) 6/r32/esi
    # return s->read >= s->write
    8b/-> *esi 1/r32/ecx
    39/compare-with *(esi+4) 1/r32/ecx
    0f 9d/set-if->= %al
$stream-empty?:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

stream-full?:  # s: (addr stream _) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    56/push-esi
    # result = false
    b8/copy-to-eax 0/imm32/false
    # esi = s
    8b/-> *(ebp+8) 6/r32/esi
    # return s->write >= s->size
    8b/-> *(esi+8) 1/r32/ecx
    39/compare-with *esi 1/r32/ecx
    0f 9d/set-if->= %al
$stream-full?:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

write-to-stream:  # s: (addr stream _), in: (addr byte), n: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    57/push-edi
    # edi = s
    8b/-> *(ebp+8) 7/r32/edi
    # var swrite/edx: int = s->write
    8b/-> *edi 2/r32/edx
    # if (swrite + n > s->size) abort
    8b/-> *(ebp+0x10) 1/r32/ecx
    01/add-to %ecx 2/r32/edx
    3b/compare 1/r32/ecx *(edi+8)
    0f 8f/jump-if-> $write-to-stream:abort/disp32
    # var out/edx: (addr byte) = s->data + s->write
    8d/copy-address *(edi+edx+0xc) 2/r32/edx
    # var outend/ebx: (addr byte) = out + n
    8b/-> *(ebp+0x10) 3/r32/ebx
    8d/copy-address *(edx+ebx) 3/r32/ebx
    # eax = in
    8b/-> *(ebp+0xc) 0/r32/eax
    # var inend/ecx: (addr byte) = in + n
    8b/-> *(ebp+0x10) 1/r32/ecx
    8d/copy-address *(eax+ecx) 1/r32/ecx
    #
    (_append-4  %edx %ebx  %eax %ecx)  # => eax
    # s->write += n
    8b/-> *(ebp+0x10) 1/r32/ecx
    01/add-to *edi 1/r32/ecx
$write-to-stream:end:
    # . restore registers
    5f/pop-to-edi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$write-to-stream:abort:
    (abort "write-to-stream: stream full")
    # never gets here

read-from-stream:  # s: (addr stream _), out: (addr byte), n: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    # esi = s
    8b/-> *(ebp+8) 6/r32/esi
    # var sread/edx: int = s->read
    8b/-> *(esi+4) 2/r32/edx
    # if (sread + n > s->write) abort
    8b/-> *(ebp+0x10) 1/r32/ecx
    01/add-to %ecx 2/r32/edx
    3b/compare 1/r32/ecx *esi
    0f 8f/jump-if-> $read-from-stream:abort/disp32
    # var in/edx: (addr byte) = s->data + s->read
    8d/copy-address *(esi+edx+0xc) 2/r32/edx
    # var inend/ebx: (addr byte) = in + n
    8b/-> *(ebp+0x10) 3/r32/ebx
    8d/copy-address *(edx+ebx) 3/r32/ebx
    # eax = out
    8b/-> *(ebp+0xc) 0/r32/eax
    # var outend/ecx: (addr byte) = out + n
    8b/-> *(ebp+0x10) 1/r32/ecx
    8d/copy-address *(eax+ecx) 1/r32/ecx
    #
    (_append-4  %eax %ecx  %edx %ebx)  # => eax
    # s->read += n
    8b/-> *(ebp+0x10) 1/r32/ecx
    01/add-to *(esi+4) 1/r32/ecx
$read-from-stream:end:
    # . restore registers
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$read-from-stream:abort:
    (abort "read-from-stream: stream empty")
    # never gets here

stream-first:  # s: (addr stream byte) -> result/eax: byte
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    56/push-esi
    # result = false
    b8/copy-to-eax 0/imm32
    # esi = s
    8b/-> *(ebp+8) 6/r32/esi
    # var idx/ecx: int = s->read
    8b/-> *(esi+4) 1/r32/ecx
    # if idx >= s->write return 0
    3b/compare-with 1/r32/ecx *esi
    7d/jump-if->= $stream-first:end/disp8
    # result = s->data[idx]
    8a/byte-> *(esi+ecx+0xc) 0/r32/AL
$stream-first:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

stream-final:  # s: (addr stream byte) -> result/eax: byte
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    56/push-esi
    # result = false
    b8/copy-to-eax 0/imm32
    # esi = s
    8b/-> *(ebp+8) 6/r32/esi
    # var max/ecx: int = s->write
    8b/-> *esi 1/r32/ecx
    # if s->read >= max return 0
    39/compare-with *(esi+4) 1/r32/ecx
    7d/jump-if->= $stream-final:end/disp8
    # var idx/ecx: int = max - 1
    49/decrement-ecx
    # result = s->data[idx]
    8a/byte-> *(esi+ecx+0xc) 0/r32/AL
$stream-final:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# compare all the data in two streams (ignoring the read pointer)
streams-data-equal?:  # a: (addr stream byte), b: (addr array byte) -> result/eax: boolean
    # pseudocode:
    #   awrite = a->write
    #   if (awrite != b->write) return false
    #   i = 0
    #   curra = a->data
    #   currb = b->data
    #   while i < awrite
    #     i1 = *curra
    #     i2 = *currb
    #     if (c1 != c2) return false
    #     i+=4, curra+=4, currb+=4
    #   return true
    #
    # registers:
    #   i: ecx
    #   awrite: edx
    #   curra: esi
    #   currb: edi
    #   i1: eax
    #   i2: ebx
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # esi = a
    8b/-> *(ebp+8) 6/r32/esi
    # edi = b
    8b/-> *(ebp+0xc) 7/r32/edi
    # var awrite/edx: int = a->write
    8b/-> *esi 2/r32/edx
$streams-data-equal?:sizes:
    # if (awrite != b->write) return false
    39/compare *edi 2/r32/edx
    75/jump-if-!= $streams-data-equal?:false/disp8
    # var curra/esi: (addr byte) = a->data
    81 0/subop/add %esi 0xc/imm32
    # var currb/edi: (addr byte) = b->data
    81 0/subop/add %edi 0xc/imm32
    # var i/ecx: int = 0
    31/xor-with %ecx 1/r32/ecx
    # var vala/eax: int
    31/xor-with %eax 0/r32/eax
    # var valb/ebx: int
    31/xor-with %ebx 3/r32/ebx
$streams-data-equal?:loop:
    {
      # if (i >= awrite) return true
      39/compare %ecx 2/r32/edx
      7d/jump-if->= $streams-data-equal?:true/disp8
      # var vala/eax: int = *curra
      8a/byte-> *esi 0/r32/eax
      # var valb/ebx: int = *currb
      8a/byte-> *edi 3/r32/ebx
      # if (vala != valb) return false
      39/compare %eax 3/r32/ebx
      75/jump-if-!= $streams-data-equal?:false/disp8
      # i++
      41/increment-ecx
      # curra++
      46/increment-esi
      # currb++
      47/increment-edi
      eb/jump loop/disp8
    }
$streams-data-equal?:true:
    b8/copy-to-eax 1/imm32
    eb/jump $streams-data-equal?:end/disp8
$streams-data-equal?:false:
    b8/copy-to-eax 0/imm32
$streams-data-equal?:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# helper for tests
check-streams-data-equal:  # s: (addr stream _), expected: (addr array _), msg: (addr array byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    #
    (streams-data-equal? *(ebp+8) *(ebp+0xc))  # => eax
    (check-ints-equal %eax 1 *(ebp+0x10))
$check-streams-data-equal:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return
699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
//: Comparison primitives

:(before "End Primitive Recipe Declarations")
EQUAL,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "equal", EQUAL);
:(before "End Primitive Recipe Checks")
case EQUAL: {
  if (SIZE(inst.ingredients) <= 1) {
    raise << maybe(get(Recipe, r).name) << "'equal' needs at least two ingredients to compare in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  const reagent& exemplar = inst.ingredients.at(0);
  for (int i = /*skip exemplar*/1;  i < SIZE(inst.ingredients);  ++i) {
    if (!types_match(inst.ingredients.at(i), exemplar) && !types_match(exemplar, inst.ingredients.at(i))) {
      raise << maybe(get(Recipe, r).name) << "'equal' expects ingredients to be all of the same type, but got '" << to_original_string(inst) << "'\n" << end();
      goto finish_checking_instruction;
    }
  }
  if (SIZE(inst.products) > 1) {
    raise << maybe(get(Recipe, r).name) << "'equal' yields exactly one product in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_boolean(inst.products.at(0))) {
    raise << maybe(get(Recipe, r).name) << "'equal' should yield a boolean, but got '" << inst.products.at(0).original_string << "'\n" << end();
    break;
  }
  break;
}
:(before "End Primitive Recipe Implementations")
case EQUAL: {
  vector<double>& exemplar = ingredients.at(0);
  bool result = true;
  for (int i = /*skip exemplar*/1;  i < SIZE(ingredients);  ++i) {
    if (SIZE(ingredients.at(i)) != SIZE(exemplar)) {
      result = false;
      break;
    }
    if (!equal(ingredients.at(i).begin(), ingredients.at(i).end(), exemplar.begin())) {
      result = false;
      break;
    }
  }
  products.resize(1);
  products.at(0).push_back(result);
  break;
}

:(code)
void test_equal() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 33\n"
      "  3:bool <- equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: location 1 is 34\n"
      "mem: location 2 is 33\n"
      "mem: storing 0 in location 3\n"
  );
}

void test_equal_2() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 34\n"
      "  3:bool <- equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: location 1 is 34\n"
      "mem: location 2 is 34\n"
      "mem: storing 1 in location 3\n"
  );
}

void test_equal_multiple() {
  run(
      "def main [\n"
      "  1:bool <- equal 34, 34, 34\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 1\n"
  );
}

void test_equal_multiple_2() {
  run(
      "def main [\n"
      "  1:bool <- equal 34, 34, 35\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 1\n"
  );
}

:(before "End Primitive Recipe Declarations")
NOT_EQUAL,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "not-equal", NOT_EQUAL);
:(before "End Primitive Recipe Checks")
case NOT_EQUAL: {
  if (SIZE(inst.ingredients) != 2) {
    raise << maybe(get(Recipe, r).name) << "'equal' needs two ingredients to compare in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  const reagent& exemplar = inst.ingredients.at(0);
  if (!types_match(inst.ingredients.at(1), exemplar) && !types_match(exemplar, inst.ingredients.at(1))) {
    raise << maybe(get(Recipe, r).name) << "'equal' expects ingredients to be all of the same type, but got '" << to_original_string(inst) << "'\n" << end();
    goto finish_checking_instruction;
  }
  if (SIZE(inst.products) > 1) {
    raise << maybe(get(Recipe, r).name) << "'equal' yields exactly one product in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_boolean(inst.products.at(0))) {
    raise << maybe(get(Recipe, r).name) << "'equal' should yield a boolean, but got '" << inst.products.at(0).original_string << "'\n" << end();
    break;
  }
  break;
}
:(before "End Primitive Recipe Implementations")
case NOT_EQUAL: {
  vector<double>& exemplar = ingredients.at(0);
  products.resize(1);
  if (SIZE(ingredients.at(1)) != SIZE(exemplar)) {
    products.at(0).push_back(true);
    break;
  }
  bool equal_ingredients = equal(ingredients.at(1).begin(), ingredients.at(1).end(), exemplar.begin());
  products.at(0).push_back(!equal_ingredients);
  break;
}

:(code)
void test_not_equal() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 33\n"
      "  3:bool <- not-equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: location 1 is 34\n"
      "mem: location 2 is 33\n"
      "mem: storing 1 in location 3\n"
  );
}

void test_not_equal_2() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 34\n"
      "  3:bool <- not-equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: location 1 is 34\n"
      "mem: location 2 is 34\n"
      "mem: storing 0 in location 3\n"
  );
}

:(before "End Primitive Recipe Declarations")
GREATER_THAN,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "greater-than", GREATER_THAN);
:(before "End Primitive Recipe Checks")
case GREATER_THAN: {
  if (SIZE(inst.ingredients) <= 1) {
    raise << maybe(get(Recipe, r).name) << "'greater-than' needs at least two ingredients to compare in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    if (!is_mu_number(inst.ingredients.at(i))) {
      raise << maybe(get(Recipe, r).name) << "'greater-than' can only compare numbers; got '" << inst.ingredients.at(i).original_string << "'\n" << end();
      goto finish_checking_instruction;
    }
  }
  if (SIZE(inst.products) > 1) {
    raise << maybe(get(Recipe, r).name) << "'greater-than' yields exactly one product in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_boolean(inst.products.at(0))) {
    raise << maybe(get(Recipe, r).name) << "'greater-than' should yield a boolean, but got '" << inst.products.at(0).original_string << "'\n" << end();
    break;
  }
  break;
}
:(before "End Primitive Recipe Implementations")
case GREATER_THAN: {
  bool result = true;
  for (int i = /**/1;  i < SIZE(ingredients);  ++i) {
    if (ingredients.at(i-1).at(0) <= ingredients.at(i).at(0)) {
      result = false;
    }
  }
  products.resize(1);
  products.at(0).push_back(result);
  break;
}

:(code)
void test_greater_than() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 33\n"
      "  3:bool <- greater-than 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 3\n"
  );
}

void test_greater_than_2() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 34\n"
      "  3:bool <- greater-than 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 3\n"
  );
}

void test_greater_than_multiple() {
  run(
      "def main [\n"
      "  1:bool <- greater-than 36, 35, 34\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 1\n"
  );
}

void test_greater_than_multiple_2() {
  run(
      "def main [\n"
      "  1:bool <- greater-than 36, 35, 35\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 1\n"
  );
}

:(before "End Primitive Recipe Declarations")
LESSER_THAN,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "lesser-than", LESSER_THAN);
:(before "End Primitive Recipe Checks")
case LESSER_THAN: {
  if (SIZE(inst.ingredients) <= 1) {
    raise << maybe(get(Recipe, r).name) << "'lesser-than' needs at least two ingredients to compare in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    if (!is_mu_number(inst.ingredients.at(i))) {
      raise << maybe(get(Recipe, r).name) << "'lesser-than' can only compare numbers; got '" << inst.ingredients.at(i).original_string << "'\n" << end();
      goto finish_checking_instruction;
    }
  }
  if (SIZE(inst.products) > 1) {
    raise << maybe(get(Recipe, r).name) << "'lesser-than' yields exactly one product in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_boolean(inst.products.at(0))) {
    raise << maybe(get(Recipe, r).name) << "'lesser-than' should yield a boolean, but got '" << inst.products.at(0).original_string << "'\n" << end();
    break;
  }
  break;
}
:(before "End Primitive Recipe Implementations")
case LESSER_THAN: {
  bool result = true;
  for (int i = /**/1;  i < SIZE(ingredients);  ++i) {
    if (ingredients.at(i-1).at(0) >= ingredients.at(i).at(0)) {
      result = false;
    }
  }
  products.resize(1);
  products.at(0).push_back(result);
  break;
}

:(code)
void test_lesser_than() {
  run(
      "def main [\n"
      "  1:num <- copy 32\n"
      "  2:num <- copy 33\n"
      "  3:bool <- lesser-than 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 3\n"
  );
}

void test_lesser_than_2() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 33\n"
      "  3:bool <- lesser-than 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 3\n"
  );
}

void test_lesser_than_multiple() {
  run(
      "def main [\n"
      "  1:bool <- lesser-than 34, 35, 36\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 1\n"
  );
}

void test_lesser_than_multiple_2() {
  run(
      "def main [\n"
      "  1:bool <- lesser-than 34, 35, 35\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 1\n"
  );
}
:(before "End Primitive Recipe Declarations")
GREATER_OR_EQUAL,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "greater-or-equal", GREATER_OR_EQUAL);
:(before "End Primitive Recipe Checks")
case GREATER_OR_EQUAL: {
  if (SIZE(inst.ingredients) <= 1) {
    raise << maybe(get(Recipe, r).name) << "'greater-or-equal' needs at least two ingredients to compare in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    if (!is_mu_number(inst.ingredients.at(i))) {
      raise << maybe(get(Recipe, r).name) << "'greater-or-equal' can only compare numbers; got '" << inst.ingredients.at(i).original_string << "'\n" << end();
      goto finish_checking_instruction;
    }
  }
  if (SIZE(inst.products) > 1) {
    raise << maybe(get(Recipe, r).name) << "'greater-or-equal' yields exactly one product in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_boolean(inst.products.at(0))) {
    raise << maybe(get(Recipe, r).name) << "'greater-or-equal' should yield a boolean, but got '" << inst.products.at(0).original_string << "'\n" << end();
    break;
  }
  break;
}
:(before "End Primitive Recipe Implementations")
case GREATER_OR_EQUAL: {
  bool result = true;
  for (int i = /**/1;  i < SIZE(ingredients);  ++i) {
    if (ingredients.at(i-1).at(0) < ingredients.at(i).at(0)) {
      result = false;
    }
  }
  products.resize(1);
  products.at(0).push_back(result);
  break;
}

:(code)
void test_greater_or_equal() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 33\n"
      "  3:bool <- greater-or-equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 3\n"
  );
}

void test_greater_or_equal_2() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 34\n"
      "  3:bool <- greater-or-equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 3\n"
  );
}

void test_greater_or_equal_3() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 35\n"
      "  3:bool <- greater-or-equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 3\n"
  );
}

void test_greater_or_equal_multiple() {
  run(
      "def main [\n"
      "  1:bool <- greater-or-equal 36, 35, 35\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 1\n"
  );
}

void test_greater_or_equal_multiple_2() {
  run(
      "def main [\n"
      "  1:bool <- greater-or-equal 36, 35, 36\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 1\n"
  );
}

:(before "End Primitive Recipe Declarations")
LESSER_OR_EQUAL,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "lesser-or-equal", LESSER_OR_EQUAL);
:(before "End Primitive Recipe Checks")
case LESSER_OR_EQUAL: {
  if (SIZE(inst.ingredients) <= 1) {
    raise << maybe(get(Recipe, r).name) << "'lesser-or-equal' needs at least two ingredients to compare in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    if (!is_mu_number(inst.ingredients.at(i))) {
      raise << maybe(get(Recipe, r).name) << "'lesser-or-equal' can only compare numbers; got '" << inst.ingredients.at(i).original_string << "'\n" << end();
      goto finish_checking_instruction;
    }
  }
  if (SIZE(inst.products) > 1) {
    raise << maybe(get(Recipe, r).name) << "'greater-or-equal' yields exactly one product in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_boolean(inst.products.at(0))) {
    raise << maybe(get(Recipe, r).name) << "'greater-or-equal' should yield a boolean, but got '" << inst.products.at(0).original_string << "'\n" << end();
    break;
  }
  break;
}
:(before "End Primitive Recipe Implementations")
case LESSER_OR_EQUAL: {
  bool result = true;
  for (int i = /**/1;  i < SIZE(ingredients);  ++i) {
    if (ingredients.at(i-1).at(0) > ingredients.at(i).at(0)) {
      result = false;
    }
  }
  products.resize(1);
  products.at(0).push_back(result);
  break;
}

:(code)
void test_lesser_or_equal() {
  run(
      "def main [\n"
      "  1:num <- copy 32\n"
      "  2:num <- copy 33\n"
      "  3:bool <- lesser-or-equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 3\n"
  );
}

void test_lesser_or_equal_2() {
  run(
      "def main [\n"
      "  1:num <- copy 33\n"
      "  2:num <- copy 33\n"
      "  3:bool <- lesser-or-equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 3\n"
  );
}

void test_lesser_or_equal_3() {
  run(
      "def main [\n"
      "  1:num <- copy 34\n"
      "  2:num <- copy 33\n"
      "  3:bool <- lesser-or-equal 1:num, 2:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 3\n"
  );
}

void test_lesser_or_equal_multiple() {
  run(
      "def main [\n"
      "  1:bool <- lesser-or-equal 34, 35, 35\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 1 in location 1\n"
  );
}

void test_lesser_or_equal_multiple_2() {
  run(
      "def main [\n"
      "  1:bool <- lesser-or-equal 34, 35, 34\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 0 in location 1\n"
  );
}

:(before "End Primitive Recipe Declarations")
MAX,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "max", MAX);
:(before "End Primitive Recipe Checks")
case MAX: {
  if (SIZE(inst.ingredients) <= 1) {
    raise << maybe(get(Recipe, r).name) << "'max' needs at least two ingredients to compare in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    if (!is_mu_number(inst.ingredients.at(i))) {
      raise << maybe(get(Recipe, r).name) << "'max' can only compare numbers; got '" << inst.ingredients.at(i).original_string << "'\n" << end();
      goto finish_checking_instruction;
    }
  }
  if (SIZE(inst.products) > 1) {
    raise << maybe(get(Recipe, r).name) << "'max' yields exactly one product in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
    raise << maybe(get(Recipe, r).name) << "'max' should yield a number, but got '" << inst.products.at(0).original_string << "'\n" << end();
    break;
  }
  break;
}
:(before "End Primitive Recipe Implementations")
case MAX: {
  int result = ingredients.at(0).at(0);
  for (int i = /**/1;  i < SIZE(ingredients);  ++i) {
    if (ingredients.at(i).at(0) > result) {
      result = ingredients.at(i).at(0);
    }
  }
  products.resize(1);
  products.at(0).push_back(result);
  break;
}

:(before "End Primitive Recipe Declarations")
MIN,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "min", MIN);
:(before "End Primitive Recipe Checks")
case MIN: {
  if (SIZE(inst.ingredients) <= 1) {
    raise << maybe(get(Recipe, r).name) << "'min' needs at least two ingredients to compare in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
    if (!is_mu_number(inst.ingredients.at(i))) {
      raise << maybe(get(Recipe, r).name) << "'min' can only compare numbers; got '" << inst.ingredients.at(i).original_string << "'\n" << end();
      goto finish_checking_instruction;
    }
  }
  if (SIZE(inst.products) > 1) {
    raise << maybe(get(Recipe, r).name) << "'min' yields exactly one product in '" << to_original_string(inst) << "'\n" << end();
    break;
  }
  if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
    raise << maybe(get(Recipe, r).name) << "'min' should yield a number, but got '" << inst.products.at(0).original_string << "'\n" << end();
    break;
  }
  break;
}
:(before "End Primitive Recipe Implementations")
case MIN: {
  int result = ingredients.at(0).at(0);
  for (int i = /**/1;  i < SIZE(ingredients);  ++i) {
    if (ingredients.at(i).at(0) < result) {
      result = ingredients.at(i).at(0);
    }
  }
  products.resize(1);
  products.at(0).push_back(result);
  break;
}