1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4// Copyright (c) 2020-2021 Jeremiasz Nelz <jeremiasz ~at~ nelz.pl>
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23
24#include "core/core.h"
25
26#if defined(TIC_BUILD_WITH_MRUBY)
27
28#include <stdlib.h>
29#include <string.h>
30#include <ctype.h>
31
32#include <mruby.h>
33#include <mruby/compile.h>
34#include <mruby/error.h>
35#include <mruby/throw.h>
36#include <mruby/array.h>
37#include <mruby/hash.h>
38#include <mruby/variable.h>
39#include <mruby/value.h>
40#include <mruby/string.h>
41
42typedef struct {
43 struct mrb_state* mrb;
44 struct mrbc_context* mrb_cxt;
45} mrbVm;
46
47static tic_core* CurrentMachine = NULL;
48static inline tic_core* getMRubyMachine(mrb_state* mrb)
49{
50 return CurrentMachine;
51}
52
53static mrb_value mrb_peek(mrb_state* mrb, mrb_value self)
54{
55 tic_core* machine = getMRubyMachine(mrb);
56 tic_mem* tic = (tic_mem*)machine;
57
58 mrb_int address;
59 mrb_int bits = BITS_IN_BYTE;
60 mrb_get_args(mrb, "i|i", &address, &bits);
61
62 return mrb_fixnum_value(tic_api_peek(tic, address, bits));
63}
64
65static mrb_value mrb_poke(mrb_state* mrb, mrb_value self)
66{
67 tic_core* machine = getMRubyMachine(mrb);
68 tic_mem* tic = (tic_mem*)machine;
69
70 mrb_int address, value;
71 mrb_int bits = BITS_IN_BYTE;
72 mrb_get_args(mrb, "ii|i", &address, &value, &bits);
73
74 tic_api_poke(tic, address, value, bits);
75
76 return mrb_nil_value();
77}
78
79static mrb_value mrb_peek1(mrb_state* mrb, mrb_value self)
80{
81 tic_core* machine = getMRubyMachine(mrb);
82 tic_mem* tic = (tic_mem*)machine;
83
84 mrb_int address;
85 mrb_get_args(mrb, "i", &address);
86
87 return mrb_fixnum_value(tic_api_peek1(tic, address));
88}
89
90static mrb_value mrb_poke1(mrb_state* mrb, mrb_value self)
91{
92 tic_core* machine = getMRubyMachine(mrb);
93 tic_mem* tic = (tic_mem*)machine;
94
95 mrb_int address, value;
96 mrb_get_args(mrb, "ii", &address, &value);
97
98 tic_api_poke1(tic, address, value);
99
100 return mrb_nil_value();
101}
102
103static mrb_value mrb_peek2(mrb_state* mrb, mrb_value self)
104{
105 tic_core* machine = getMRubyMachine(mrb);
106 tic_mem* tic = (tic_mem*)machine;
107
108 mrb_int address;
109 mrb_get_args(mrb, "i", &address);
110
111 return mrb_fixnum_value(tic_api_peek2(tic, address));
112}
113
114static mrb_value mrb_poke2(mrb_state* mrb, mrb_value self)
115{
116 tic_core* machine = getMRubyMachine(mrb);
117 tic_mem* tic = (tic_mem*)machine;
118
119 mrb_int address, value;
120 mrb_get_args(mrb, "ii", &address, &value);
121
122 tic_api_poke2(tic, address, value);
123
124 return mrb_nil_value();
125}
126
127static mrb_value mrb_peek4(mrb_state* mrb, mrb_value self)
128{
129 tic_core* machine = getMRubyMachine(mrb);
130 tic_mem* tic = (tic_mem*)machine;
131
132 mrb_int address;
133 mrb_get_args(mrb, "i", &address);
134
135 return mrb_fixnum_value(tic_api_peek4(tic, address));
136}
137
138static mrb_value mrb_poke4(mrb_state* mrb, mrb_value self)
139{
140 tic_core* machine = getMRubyMachine(mrb);
141 tic_mem* tic = (tic_mem*)machine;
142
143 mrb_int address, value;
144 mrb_get_args(mrb, "ii", &address, &value);
145
146 tic_api_poke4(tic, address, value);
147
148 return mrb_nil_value();
149}
150
151static mrb_value mrb_cls(mrb_state* mrb, mrb_value self)
152{
153 mrb_int color = 0;
154 mrb_get_args(mrb, "|i", &color);
155
156 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
157
158 tic_api_cls(memory, color);
159
160 return mrb_nil_value();
161}
162
163static mrb_value mrb_pix(mrb_state* mrb, mrb_value self)
164{
165 mrb_int x, y, color;
166 mrb_int argc = mrb_get_args(mrb, "ii|i", &x, &y, &color);
167
168 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
169
170 if(argc == 3)
171 {
172 tic_api_pix(memory, x, y, color, false);
173 return mrb_nil_value();
174 }
175 else
176 {
177 return mrb_fixnum_value(tic_api_pix(memory, x, y, 0, true));
178 }
179}
180
181static mrb_value mrb_line(mrb_state* mrb, mrb_value self)
182{
183 mrb_float x0, y0, x1, y1;
184 mrb_int color;
185 mrb_get_args(mrb, "ffffi", &x0, &y0, &x1, &y1, &color);
186
187 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
188
189 tic_api_line(memory, x0, y0, x1, y1, color);
190
191 return mrb_nil_value();
192}
193
194static mrb_value mrb_rect(mrb_state* mrb, mrb_value self)
195{
196 mrb_int x, y, w, h, color;
197 mrb_get_args(mrb, "iiiii", &x, &y, &w, &h, &color);
198
199 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
200
201 tic_api_rect(memory, x, y, w, h, color);
202
203 return mrb_nil_value();
204}
205
206static mrb_value mrb_rectb(mrb_state* mrb, mrb_value self)
207{
208 mrb_int x, y, w, h, color;
209 mrb_get_args(mrb, "iiiii", &x, &y, &w, &h, &color);
210
211 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
212
213 tic_api_rectb(memory, x, y, w, h, color);
214
215 return mrb_nil_value();
216}
217
218static mrb_value mrb_circ(mrb_state* mrb, mrb_value self)
219{
220 mrb_int x, y, radius, color;
221 mrb_get_args(mrb, "iiii", &x, &y, &radius, &color);
222
223 if(radius >= 0) {
224 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
225
226 tic_api_circ(memory, x, y, radius, color);
227 } else {
228 mrb_raise(mrb, E_ARGUMENT_ERROR, "radius must be greater than or equal 0");
229 }
230
231 return mrb_nil_value();
232}
233
234static mrb_value mrb_circb(mrb_state* mrb, mrb_value self)
235{
236 mrb_int x, y, radius, color;
237 mrb_get_args(mrb, "iiii", &x, &y, &radius, &color);
238
239 if(radius >= 0) {
240 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
241
242 tic_api_circb(memory, x, y, radius, color);
243 } else {
244 mrb_raise(mrb, E_ARGUMENT_ERROR, "radius must be greater than or equal 0");
245 }
246
247 return mrb_nil_value();
248}
249
250static mrb_value mrb_elli(mrb_state* mrb, mrb_value self)
251{
252 mrb_int x, y, a, b, color;
253 mrb_get_args(mrb, "iiiii", &x, &y, &a, &b, &color);
254
255 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
256
257 tic_api_elli(memory, x, y, a, b, color);
258
259 return mrb_nil_value();
260}
261
262static mrb_value mrb_ellib(mrb_state* mrb, mrb_value self)
263{
264 mrb_int x, y, a, b, color;
265 mrb_get_args(mrb, "iiiii", &x, &y, &a, &b, &color);
266
267 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
268
269 tic_api_ellib(memory, x, y, a, b, color);
270
271 return mrb_nil_value();
272}
273
274static mrb_value mrb_tri(mrb_state* mrb, mrb_value self)
275{
276 mrb_float x1, y1, x2, y2, x3, y3;
277 mrb_int color;
278 mrb_get_args(mrb, "ffffffi", &x1, &y1, &x2, &y2, &x3, &y3, &color);
279
280 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
281
282 tic_api_tri(memory, x1, y1, x2, y2, x3, y3, color);
283
284 return mrb_nil_value();
285}
286
287static mrb_value mrb_trib(mrb_state* mrb, mrb_value self)
288{
289 mrb_float x1, y1, x2, y2, x3, y3;
290 mrb_int color;
291 mrb_get_args(mrb, "ffffffi", &x1, &y1, &x2, &y2, &x3, &y3, &color);
292
293 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
294
295 tic_api_trib(memory, x1, y1, x2, y2, x3, y3, color);
296
297 return mrb_nil_value();
298}
299
300static mrb_value mrb_ttri(mrb_state* mrb, mrb_value self)
301{
302 mrb_value chroma = mrb_fixnum_value(0xff);
303 mrb_int src = tic_tiles_texture;
304
305 mrb_float x1, y1, x2, y2, x3, y3, u1, v1, u2, v2, u3, v3, z1, z2, z3;
306 mrb_int argc = mrb_get_args(mrb, "ffffffffffff|iofff",
307 &x1, &y1, &x2, &y2, &x3, &y3,
308 &u1, &v1, &u2, &v2, &u3, &v3,
309 &src, &chroma, &z1, &z2, &z3);
310
311 mrb_int count;
312 u8 *chromas;
313 if (mrb_array_p(chroma))
314 {
315 count = ARY_LEN(RARRAY(chroma));
316 chromas = malloc(count * sizeof(u8));
317
318 for (mrb_int i = 0; i < count; ++i)
319 {
320 chromas[i] = mrb_integer(mrb_ary_entry(chroma, i));
321 }
322 }
323 else
324 {
325 count = 1;
326 chromas = malloc(sizeof(u8));
327 chromas[0] = mrb_integer(chroma);
328 }
329
330 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
331
332 tic_api_ttri(memory,
333 x1, y1, x2, y2, x3, y3,
334 u1, v1, u2, v2, u3, v3,
335 src, chromas, count, z1, z2, z3, argc == 17);
336
337 free(chromas);
338
339 return mrb_nil_value();
340}
341
342
343static mrb_value mrb_clip(mrb_state* mrb, mrb_value self)
344{
345 mrb_int x, y, w, h;
346 mrb_int argc = mrb_get_args(mrb, "|iiii", &x, &y, &w, &h);
347
348 if(argc == 0)
349 {
350 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
351
352 tic_api_clip(memory, 0, 0, TIC80_WIDTH, TIC80_HEIGHT);
353 }
354 else if(argc == 4)
355 {
356 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
357
358 tic_api_clip((tic_mem*)getMRubyMachine(mrb), x, y, w, h);
359 }
360 else mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid parameters, use clip(x,y,w,h) or clip()");
361
362 return mrb_nil_value();
363}
364
365static mrb_value mrb_btnp(mrb_state* mrb, mrb_value self)
366{
367 tic_core* machine = getMRubyMachine(mrb);
368 tic_mem* memory = (tic_mem*)machine;
369
370 mrb_int index, hold, period;
371 mrb_int argc = mrb_get_args(mrb, "|iii", &index, &hold, &period);
372
373 index &= 0x1f;
374
375 if (argc == 0)
376 {
377 return mrb_fixnum_value(tic_api_btnp(memory, -1, -1, -1));
378 }
379 else if(argc == 1)
380 {
381 return mrb_bool_value(tic_api_btnp(memory, index, -1, -1));
382 }
383 else if (argc == 3)
384 {
385 return mrb_bool_value(tic_api_btnp(memory, index, hold, period));
386 }
387 else
388 {
389 mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid params, btnp [ id [ hold period ] ]");
390 return mrb_nil_value();
391 }
392}
393
394static mrb_value mrb_btn(mrb_state* mrb, mrb_value self)
395{
396 tic_core* machine = getMRubyMachine(mrb);
397
398 mrb_int index, hold, period;
399 mrb_int argc = mrb_get_args(mrb, "|i", &index, &hold, &period);
400
401 index &= 0x1f;
402
403 if (argc == 0)
404 {
405 return mrb_fixnum_value(machine->memory.ram->input.gamepads.data);
406 }
407 else if (argc == 1)
408 {
409 return mrb_bool_value(machine->memory.ram->input.gamepads.data & (1 << index));
410 }
411 else
412 {
413 mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid params, btn [ id ]\n");
414 return mrb_nil_value();
415 }
416}
417
418static mrb_value mrb_spr(mrb_state* mrb, mrb_value self)
419{
420 mrb_int index, x, y;
421 mrb_int w = 1, h = 1, scale = 1;
422 mrb_int flip = tic_no_flip, rotate = tic_no_rotate;
423 mrb_value colors_obj;
424 static u8 colors[TIC_PALETTE_SIZE];
425 mrb_int count = 0;
426
427 mrb_int argc = mrb_get_args(mrb, "iii|oiiiii", &index, &x, &y, &colors_obj, &scale, &flip, &rotate, &w, &h);
428
429 if(mrb_array_p(colors_obj))
430 {
431 for(; count < TIC_PALETTE_SIZE && count < ARY_LEN(RARRAY(colors_obj)); count++) // HACK
432 colors[count] = (u8) mrb_int(mrb, mrb_ary_entry(colors_obj, count));
433 count++;
434 }
435 else if(mrb_fixnum_p(colors_obj))
436 {
437 colors[0] = mrb_int(mrb, colors_obj);
438 count = 1;
439 }
440 else
441 {
442 mrb_raise(mrb, E_ARGUMENT_ERROR, "color must be either an array or a palette index");
443 return mrb_nil_value();
444 }
445
446 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
447
448 tic_api_spr(memory, index, x, y, w, h, colors, count, scale, flip, rotate);
449
450 return mrb_nil_value();
451}
452
453static mrb_value mrb_mget(mrb_state* mrb, mrb_value self)
454{
455 mrb_int x, y;
456 mrb_get_args(mrb, "ii", &x, &y);
457
458 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
459
460 u8 value = tic_api_mget(memory, x, y);
461 return mrb_fixnum_value(value);
462}
463
464static mrb_value mrb_mset(mrb_state* mrb, mrb_value self)
465{
466 mrb_int x, y, val;
467 mrb_get_args(mrb, "iii", &x, &y, &val);
468
469 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
470
471 tic_api_mset(memory, x, y, val);
472
473 return mrb_nil_value();
474}
475
476static mrb_value mrb_tstamp(mrb_state* mrb, mrb_value self)
477{
478 tic_mem* tic = (tic_mem*)getMRubyMachine(mrb);
479
480 return mrb_fixnum_value(tic_api_tstamp(tic));
481}
482
483static mrb_value mrb_vbank(mrb_state* mrb, mrb_value self)
484{
485 tic_core *core = getMRubyMachine(mrb);
486 tic_mem *tic = (tic_mem*)core;
487
488 s32 prev = core->state.vbank.id;
489
490 mrb_int vbank;
491 mrb_int argc = mrb_get_args(mrb, "|i", &vbank);
492
493 if (argc >= 1)
494 {
495 tic_api_vbank(tic, vbank);
496 }
497
498 return mrb_fixnum_value(prev);
499}
500
501static mrb_value mrb_fget(mrb_state* mrb, mrb_value self)
502{
503 mrb_int index, flag;
504 mrb_get_args(mrb, "ii", &index, &flag);
505
506 tic_mem* tic = (tic_mem*)getMRubyMachine(mrb);
507
508 return mrb_bool_value(tic_api_fget(tic, index, flag));
509}
510
511static mrb_value mrb_fset(mrb_state* mrb, mrb_value self)
512{
513 mrb_int index, flag;
514 mrb_bool value;
515 mrb_get_args(mrb, "iib", &index, &flag, &value);
516
517 tic_mem* tic = (tic_mem*)getMRubyMachine(mrb);
518
519 tic_api_fset(tic, index, flag, value);
520
521 return mrb_nil_value();
522}
523
524typedef struct
525{
526 mrb_state* mrb;
527 mrb_value block;
528} RemapData;
529
530static void remapCallback(void* data, s32 x, s32 y, RemapResult* result)
531{
532 RemapData* remap = (RemapData*)data;
533 mrb_state* mrb = remap->mrb;
534
535 mrb_value vals[] = {
536 mrb_fixnum_value(result->index),
537 mrb_fixnum_value(x),
538 mrb_fixnum_value(y)
539 };
540 mrb_value out = mrb_yield_argv(mrb, remap->block, 3, vals);
541
542 if (mrb_array_p(out))
543 {
544 mrb_int len = ARY_LEN(RARRAY(out));
545 if (len > 3) len = 3;
546 switch (len)
547 {
548 case 3:
549 result->rotate = mrb_int(mrb, mrb_ary_entry(out, 2));
550 case 2:
551 result->flip = mrb_int(mrb, mrb_ary_entry(out, 1));
552 case 1:
553 result->index = mrb_int(mrb, mrb_ary_entry(out, 0));
554 }
555 }
556 else
557 {
558 result->index = mrb_int(mrb, out);
559 }
560}
561
562static mrb_value mrb_map(mrb_state* mrb, mrb_value self)
563{
564 mrb_int x = 0;
565 mrb_int y = 0;
566 mrb_int w = TIC_MAP_SCREEN_WIDTH;
567 mrb_int h = TIC_MAP_SCREEN_HEIGHT;
568 mrb_int sx = 0;
569 mrb_int sy = 0;
570 mrb_value chroma;
571 mrb_int scale = 1;
572 mrb_value block;
573
574 mrb_int argc = mrb_get_args(mrb, "|iiiiiioi&", &x, &y, &w, &h, &sx, &sy, &chroma, &scale, &block);
575
576 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
577
578 mrb_int count;
579 u8 *chromas;
580
581 if (mrb_array_p(chroma))
582 {
583 count = ARY_LEN(RARRAY(chroma));
584 chromas = malloc(count * sizeof(u8));
585
586 for (mrb_int i = 0; i < count; ++i)
587 {
588 chromas[i] = mrb_integer(mrb_ary_entry(chroma, i));
589 }
590 }
591 else
592 {
593 count = 1;
594 chromas = malloc(sizeof(u8));
595 chromas[0] = mrb_integer(chroma);
596 }
597
598 if (mrb_proc_p(block))
599 {
600 RemapData data = { mrb, block };
601 tic_api_map(memory, x, y, w, h, sx, sy, chromas, count, scale, remapCallback, &data);
602 }
603 else
604 {
605 tic_api_map(memory, x, y, w, h, sx, sy, chromas, count, scale, NULL, NULL);
606 }
607
608 free(chromas);
609
610 return mrb_nil_value();
611}
612
613static mrb_value mrb_music(mrb_state* mrb, mrb_value self)
614{
615 mrb_int track = 0;
616 mrb_int frame = -1;
617 mrb_int row = -1;
618 bool loop = true;
619 bool sustain = false;
620 mrb_int tempo = -1;
621 mrb_int speed = -1;
622
623 mrb_int argc = mrb_get_args(mrb, "|iiibbii", &track, &frame, &row, &loop, &sustain, &tempo, &speed);
624
625 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
626
627 tic_api_music(memory, -1, 0, 0, false, false, -1, -1);
628
629 if(track >= 0)
630 {
631 if(track > MUSIC_TRACKS - 1)
632 mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid music track index");
633 else
634 tic_api_music(memory, track, frame, row, loop, sustain, tempo, speed);
635 }
636
637 return mrb_nil_value();
638}
639
640static mrb_value mrb_sfx(mrb_state* mrb, mrb_value self)
641{
642 mrb_int index;
643
644 mrb_value note_obj;
645 s32 note = -1;
646 mrb_int octave = -1;
647 mrb_int duration = -1;
648 mrb_int channel = 0;
649 mrb_value volume = mrb_int_value(mrb, MAX_VOLUME);
650 mrb_int volumes[TIC80_SAMPLE_CHANNELS];
651 mrb_int speed = SFX_DEF_SPEED;
652
653 mrb_int argc = mrb_get_args(mrb, "i|oiio!i", &index, &note_obj, &duration, &channel, &volume, &speed);
654
655 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
656
657 if (mrb_array_p(volume))
658 {
659 for (mrb_int i = 0; i < TIC80_SAMPLE_CHANNELS; ++i)
660 {
661 volumes[i] = mrb_integer(mrb_ary_entry(volume, i));
662 }
663 }
664 else if (mrb_fixnum_p(volume))
665 {
666 for (size_t ch = 0; ch < TIC80_SAMPLE_CHANNELS; ++ch)
667 {
668 volumes[ch] = mrb_integer(volume);
669 }
670 }
671 else
672 {
673 mrb_raise(mrb, E_ARGUMENT_ERROR, "volume must be an integer or a array of integers per channel");
674 return mrb_nil_value();
675 }
676
677 if(index < SFX_COUNT)
678 {
679 if (index >= 0)
680 {
681 tic_sample* effect = memory->ram->sfx.samples.data + index;
682
683 note = effect->note;
684 octave = effect->octave;
685 speed = effect->speed;
686 }
687
688 if (argc >= 2)
689 {
690 if (mrb_fixnum_p(note_obj))
691 {
692 mrb_int id = mrb_integer(note_obj);
693 note = id % NOTES;
694 octave = id / NOTES;
695 }
696 else if (mrb_string_p(note_obj))
697 {
698 const char* noteStr = mrb_str_to_cstr(mrb, mrb_funcall(mrb, note_obj, "to_s", 0));
699
700 s32 octave32;
701 if (!tic_tool_parse_note(noteStr, &note, &octave32))
702 {
703 mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid note, should be like C#4");
704 return mrb_nil_value();
705 }
706 octave = octave32;
707 }
708 else
709 {
710 mrb_raise(mrb, E_ARGUMENT_ERROR, "note must be either an integer number or a string like \"C#4\"");
711 return mrb_nil_value();
712 }
713 }
714
715 if (channel >= 0 && channel < TIC_SOUND_CHANNELS)
716 {
717 tic_api_sfx(memory, index, note, octave, duration, channel, volumes[0] & 0xf, volumes[1] & 0xf, speed);
718 }
719 else mrb_raise(mrb, E_ARGUMENT_ERROR, "unknown channel");
720 }
721 else mrb_raise(mrb, E_ARGUMENT_ERROR, "unknown sfx index");
722
723 return mrb_nil_value();
724}
725
726static mrb_value mrb_sync(mrb_state* mrb, mrb_value self)
727{
728 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
729
730 mrb_int mask = 0;
731 mrb_int bank = 0;
732 mrb_bool toCart = false;
733
734 mrb_int argc = mrb_get_args(mrb, "|iib", &mask, &bank, &toCart);
735
736 if(bank >= 0 && bank < TIC_BANKS)
737 tic_api_sync(memory, mask, bank, toCart);
738 else
739 mrb_raise(mrb, E_ARGUMENT_ERROR, "sync() error, invalid bank");
740
741 return mrb_nil_value();
742}
743
744static mrb_value mrb_reset(mrb_state* mrb, mrb_value self)
745{
746 tic_core* machine = getMRubyMachine(mrb);
747
748 machine->state.initialized = false;
749
750 return mrb_nil_value();
751}
752
753static mrb_value mrb_key(mrb_state* mrb, mrb_value self)
754{
755 tic_core* machine = getMRubyMachine(mrb);
756 tic_mem* tic = &machine->memory;
757
758 mrb_int key;
759 mrb_int argc = mrb_get_args(mrb, "|i", &key);
760
761 if (argc == 0)
762 return mrb_bool_value(tic_api_key(tic, tic_key_unknown));
763 else
764 {
765 if(key < tic_key_escape)
766 return mrb_bool_value(tic_api_key(tic, key));
767 else
768 {
769 mrb_raise(mrb, E_ARGUMENT_ERROR, "unknown keyboard code");
770 return mrb_nil_value();
771 }
772 }
773}
774
775static mrb_value mrb_keyp(mrb_state* mrb, mrb_value self)
776{
777 tic_core* machine = getMRubyMachine(mrb);
778 tic_mem* memory = (tic_mem*)machine;
779
780 mrb_int key, hold, period;
781 mrb_int argc = mrb_get_args(mrb, "|iii", &key, &hold, &period);
782
783 if (argc == 0)
784 {
785 return mrb_fixnum_value(tic_api_keyp(memory, -1, -1, -1));
786 }
787 else if (key >= tic_key_escape)
788 {
789 mrb_raise(mrb, E_ARGUMENT_ERROR, "unknown keyboard code");
790 }
791 else if(argc == 1)
792 {
793 return mrb_bool_value(tic_api_keyp(memory, key, -1, -1));
794 }
795 else if (argc == 3)
796 {
797 return mrb_bool_value(tic_api_keyp(memory, key, hold, period));
798 }
799 else
800 {
801 mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid params, btnp [ id [ hold period ] ]");
802 return mrb_nil_value();
803 }
804}
805
806static mrb_value mrb_memcpy(mrb_state* mrb, mrb_value self)
807{
808 mrb_int dest, src, size;
809 mrb_int argc = mrb_get_args(mrb, "iii", &dest, &src, &size);
810
811 s32 bound = sizeof(tic_ram) - size;
812
813 if(size >= 0 && size <= sizeof(tic_ram) && dest >= 0 && src >= 0 && dest <= bound && src <= bound)
814 {
815 u8* base = (u8*)&getMRubyMachine(mrb)->memory;
816 memcpy(base + dest, base + src, size);
817 }
818 else
819 {
820 mrb_raise(mrb, E_ARGUMENT_ERROR, "memory address not in range!");
821 }
822
823 return mrb_nil_value();
824}
825
826static mrb_value mrb_memset(mrb_state* mrb, mrb_value self)
827{
828 mrb_int dest, value, size;
829 mrb_int argc = mrb_get_args(mrb, "iii", &dest, &value, &size);
830
831 s32 bound = sizeof(tic_ram) - size;
832
833 if(size >= 0 && size <= sizeof(tic_ram) && dest >= 0 && dest <= bound)
834 {
835 u8* base = (u8*)&getMRubyMachine(mrb)->memory;
836 memset(base + dest, value, size);
837 }
838 else
839 {
840 mrb_raise(mrb, E_ARGUMENT_ERROR, "memory address not in range!");
841 }
842
843 return mrb_nil_value();
844}
845
846static char* mrb_value_to_cstr(mrb_state* mrb, mrb_value value)
847{
848 mrb_value str = mrb_funcall(mrb, value, "to_s", 0);
849 return mrb_str_to_cstr(mrb, str);
850}
851
852static mrb_value mrb_font(mrb_state* mrb, mrb_value self)
853{
854 mrb_value text_obj;
855 mrb_int x = 0;
856 mrb_int y = 0;
857 mrb_int width = TIC_SPRITESIZE;
858 mrb_int height = TIC_SPRITESIZE;
859 mrb_int chromakey = 0;
860 mrb_bool fixed = false;
861 mrb_int scale = 1;
862 mrb_bool alt = false;
863 mrb_get_args(mrb, "S|iiiiibib",
864 &text_obj, &x, &y, &chromakey,
865 &width, &height, &fixed, &scale, &alt);
866
867 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
868
869 if(scale == 0)
870 return mrb_fixnum_value(0);
871
872 const char* text = mrb_value_to_cstr(mrb, text_obj);
873
874 s32 size = tic_api_font(memory, text, x, y, (u8*)&chromakey, 1, width, height, scale, fixed, alt);
875 return mrb_fixnum_value(size);
876}
877
878static mrb_value mrb_print(mrb_state* mrb, mrb_value self)
879{
880 mrb_value text_obj;
881 mrb_int x = 0;
882 mrb_int y = 0;
883 mrb_int color = TIC_PALETTE_SIZE-1;
884 mrb_bool fixed = false;
885 mrb_int scale = 1;
886 mrb_bool alt = false;
887 mrb_get_args(mrb, "S|iiibib",
888 &text_obj, &x, &y, &color,
889 &fixed, &scale, &alt);
890
891 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
892
893 if(scale == 0)
894 return mrb_fixnum_value(0);
895
896 const char* text = mrb_str_to_cstr(mrb, text_obj);
897
898 s32 size = tic_api_print(memory, text, x, y, color, fixed, scale, alt);
899 return mrb_fixnum_value(size);
900}
901
902static mrb_value mrb_trace(mrb_state *mrb, mrb_value self)
903{
904 mrb_value text_obj;
905 mrb_int color = TIC_DEFAULT_COLOR;
906 mrb_get_args(mrb, "S|i", &text_obj, &color);
907
908 tic_core* machine = getMRubyMachine(mrb);
909
910 const char* text = mrb_value_to_cstr(mrb, text_obj);
911 machine->data->trace(machine->data->data, text, color);
912
913 return mrb_nil_value();
914}
915
916static mrb_value mrb_pmem(mrb_state *mrb, mrb_value self)
917{
918 mrb_int index, value;
919 mrb_int argc = mrb_get_args(mrb, "i|i", &index, &value);
920
921 tic_core* machine = getMRubyMachine(mrb);
922 tic_mem* memory = &machine->memory;
923
924 if(index < TIC_PERSISTENT_SIZE)
925 {
926 u32 val = tic_api_pmem((tic_mem*)machine, index, 0, false);
927
928 if(argc == 2)
929 {
930 u32 val = tic_api_pmem((tic_mem*)machine, index, value, true);
931 }
932
933 return mrb_fixnum_value(val);
934 }
935 else
936 {
937 mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid persistent memory index");
938
939 return mrb_nil_value();
940 }
941}
942
943static mrb_value mrb_time(mrb_state *mrb, mrb_value self)
944{
945 tic_mem* memory = (tic_mem*)getMRubyMachine(mrb);
946 return mrb_float_value(mrb, tic_api_time(memory));
947}
948
949static mrb_value mrb_exit(mrb_state *mrb, mrb_value self)
950{
951 tic_core* machine = getMRubyMachine(mrb);
952 machine->data->exit(machine->data->data);
953
954 return mrb_nil_value();
955}
956
957static mrb_value mrb_mouse(mrb_state *mrb, mrb_value self)
958{
959 mrb_value sym_x = mrb_symbol_value(mrb_intern_cstr(mrb, "x"));
960 mrb_value sym_y = mrb_symbol_value(mrb_intern_cstr(mrb, "y"));
961 mrb_value sym_left = mrb_symbol_value(mrb_intern_cstr(mrb, "left"));
962 mrb_value sym_middle = mrb_symbol_value(mrb_intern_cstr(mrb, "middle"));
963 mrb_value sym_right = mrb_symbol_value(mrb_intern_cstr(mrb, "right"));
964
965 tic_core* machine = getMRubyMachine(mrb);
966
967 const tic80_mouse* mouse = &machine->memory.ram->input.mouse;
968
969 mrb_value hash = mrb_hash_new(mrb);
970
971 mrb_hash_set(mrb, hash, sym_x, mrb_fixnum_value(mouse->x));
972 mrb_hash_set(mrb, hash, sym_y, mrb_fixnum_value(mouse->y));
973 mrb_hash_set(mrb, hash, sym_left, mrb_bool_value(mouse->left));
974 mrb_hash_set(mrb, hash, sym_middle, mrb_bool_value(mouse->middle));
975 mrb_hash_set(mrb, hash, sym_right, mrb_bool_value(mouse->right));
976
977 return hash;
978}
979
980static void closeMRuby(tic_mem* tic)
981{
982 tic_core* machine = (tic_core*)tic;
983
984 if(machine->currentVM)
985 {
986 mrbVm *currentVM = (mrbVm*)machine->currentVM;
987 mrbc_context_free(currentVM->mrb, currentVM->mrb_cxt);
988 mrb_close(currentVM->mrb);
989 currentVM->mrb_cxt = NULL;
990 currentVM->mrb = NULL;
991
992 free(currentVM);
993 CurrentMachine = machine->currentVM = NULL;
994 }
995}
996
997static mrb_bool catcherr(tic_core* machine)
998{
999 mrb_state* mrb = ((mrbVm*)machine->currentVM)->mrb;
1000 if (mrb->exc)
1001 {
1002 mrb_value ex = mrb_obj_value(mrb->exc);
1003 mrb_value bt = mrb_exc_backtrace(mrb, ex);
1004 if (!mrb_array_p(bt))
1005 bt = mrb_get_backtrace(mrb);
1006 mrb_value insp = mrb_inspect(mrb, ex);
1007 mrb_ary_unshift(mrb, bt, insp);
1008 mrb_value msg = mrb_ary_join(mrb, bt, mrb_str_new_cstr(mrb, "\n"));
1009 char* cmsg = mrb_str_to_cstr(mrb, msg);
1010 machine->data->error(machine->data->data, cmsg);
1011 mrb->exc = NULL;
1012
1013 return false;
1014 }
1015
1016 return true;
1017}
1018
1019static bool initMRuby(tic_mem* tic, const char* code)
1020{
1021 tic_core* machine = (tic_core*)tic;
1022
1023 closeMRuby(tic);
1024
1025 CurrentMachine = machine;
1026
1027 machine->currentVM = malloc(sizeof(mrbVm));
1028 mrbVm *currentVM = (mrbVm*)machine->currentVM;
1029
1030 mrb_state* mrb = currentVM->mrb = mrb_open();
1031 mrbc_context* mrb_cxt = currentVM->mrb_cxt = mrbc_context_new(mrb);
1032 mrb_cxt->capture_errors = 1;
1033 mrbc_filename(mrb, mrb_cxt, "user code");
1034
1035#define API_FUNC_DEF(name, _, __, nparam, nrequired, callback, ...) {mrb_ ## name, nrequired, (nparam - nrequired), callback, #name},
1036 static const struct{mrb_func_t func; s32 nrequired; s32 noptional; bool block; const char* name;} ApiItems[] = {TIC_API_LIST(API_FUNC_DEF)};
1037#undef API_FUNC_DEF
1038
1039 for (s32 i = 0; i < COUNT_OF(ApiItems); ++i)
1040 {
1041 mrb_aspec args = MRB_ARGS_NONE();
1042 if (ApiItems[i].nrequired > 0)
1043 args |= MRB_ARGS_REQ(ApiItems[i].nrequired);
1044 if (ApiItems[i].noptional > 0)
1045 args |= MRB_ARGS_OPT(ApiItems[i].noptional);
1046 if (ApiItems[i].block)
1047 args |= MRB_ARGS_BLOCK();
1048
1049 mrb_define_method(mrb, mrb->kernel_module, ApiItems[i].name, ApiItems[i].func, args);
1050 }
1051
1052 mrb_load_string_cxt(mrb, code, mrb_cxt);
1053 return catcherr(machine);
1054}
1055
1056static void evalMRuby(tic_mem* tic, const char* code) {
1057 tic_core* machine = (tic_core*)tic;
1058
1059 mrb_state* mrb = ((mrbVm*)machine->currentVM)->mrb = mrb_open();
1060
1061 if (!mrb)
1062 return;
1063
1064 if (((mrbVm*)machine->currentVM)->mrb_cxt)
1065 {
1066 mrbc_context_free(mrb, ((mrbVm*)machine->currentVM)->mrb_cxt);
1067 }
1068
1069 mrbc_context* mrb_cxt = ((mrbVm*)machine->currentVM)->mrb_cxt = mrbc_context_new(mrb);
1070 mrb_load_string_cxt(mrb, code, mrb_cxt);
1071 catcherr(machine);
1072}
1073
1074static void callMRubyTick(tic_mem* tic)
1075{
1076 tic_core* machine = (tic_core*)tic;
1077 const char* TicFunc = TIC_FN;
1078
1079 mrb_state* mrb = ((mrbVm*)machine->currentVM)->mrb;
1080
1081 if(mrb)
1082 {
1083 if (mrb_respond_to(mrb, mrb_top_self(mrb), mrb_intern_cstr(mrb, TicFunc)))
1084 {
1085 mrb_funcall(mrb, mrb_top_self(mrb), TicFunc, 0);
1086 catcherr(machine);
1087 }
1088 else
1089 {
1090 machine->data->error(machine->data->data, "'def TIC...' isn't found :(");
1091 }
1092 }
1093}
1094
1095static void callMRubyBoot(tic_mem* tic)
1096{
1097 tic_core* machine = (tic_core*)tic;
1098 const char* BootFunc = BOOT_FN;
1099
1100 mrb_state* mrb = ((mrbVm*)machine->currentVM)->mrb;
1101
1102 if(mrb)
1103 {
1104 if (mrb_respond_to(mrb, mrb_top_self(mrb), mrb_intern_cstr(mrb, BootFunc)))
1105 {
1106 mrb_funcall(mrb, mrb_top_self(mrb), BootFunc, 0);
1107 catcherr(machine);
1108 }
1109 }
1110}
1111
1112static void callMRubyIntCallback(tic_mem* memory, s32 value, void* data, const char* name)
1113{
1114 tic_core* machine = (tic_core*)memory;
1115 mrb_state* mrb = ((mrbVm*)machine->currentVM)->mrb;
1116
1117 if (mrb && mrb_respond_to(mrb, mrb_top_self(mrb), mrb_intern_cstr(mrb, name)))
1118 {
1119 mrb_funcall(mrb, mrb_top_self(mrb), name, 1, mrb_fixnum_value(value));
1120 catcherr(machine);
1121 }
1122}
1123
1124static void callMRubyScanline(tic_mem* memory, s32 row, void* data)
1125{
1126 callMRubyIntCallback(memory, row, data, SCN_FN);
1127
1128 callMRubyIntCallback(memory, row, data, "scanline");
1129}
1130
1131static void callMRubyBorder(tic_mem* memory, s32 row, void* data)
1132{
1133 callMRubyIntCallback(memory, row, data, BDR_FN);
1134}
1135
1136static void callMRubyMenu(tic_mem* memory, s32 index, void* data)
1137{
1138 callMRubyIntCallback(memory, index, data, MENU_FN);
1139}
1140
1141/**
1142 * External Resources
1143 * ==================
1144 *
1145 * [Outdated official documentation regarding syntax](https://ruby-doc.org/docs/ruby-doc-bundle/Manual/man-1.4/syntax.html#resword)
1146 * [Ruby for dummies reserved word listing](https://www.dummies.com/programming/ruby/rubys-reserved-words/)
1147 */
1148static const char* const MRubyKeywords [] =
1149{
1150 "BEGIN", "class", "ensure", "nil", "self", "when",
1151 "END", "def", "false", "not", "super", "while",
1152 "alias", "defined", "for", "or", "then", "yield",
1153 "and", "do", "if", "redo", "true",
1154 "begin", "else", "in", "rescue", "undef",
1155 "break", "elsif", "module", "retry", "unless",
1156 "case", "end", "next", "return", "until",
1157 "__FILE__", "__LINE__"
1158};
1159
1160static inline bool isalnum_(char c) {return isalnum(c) || c == '_' || c == '?' || c == '=' || c == '!';}
1161
1162static const tic_outline_item* getMRubyOutline(const char* code, s32* size)
1163{
1164 enum{Size = sizeof(tic_outline_item)};
1165
1166 *size = 0;
1167
1168 static tic_outline_item* items = NULL;
1169
1170 if(items)
1171 {
1172 free(items);
1173 items = NULL;
1174 }
1175
1176 const char* ptr = code;
1177
1178 while(true)
1179 {
1180 static const char FuncString[] = "def ";
1181
1182 ptr = strstr(ptr, FuncString);
1183
1184 if(ptr)
1185 {
1186 ptr += sizeof FuncString - 1;
1187
1188 const char* start = ptr;
1189 const char* end = start;
1190
1191 while(*ptr)
1192 {
1193 char c = *ptr;
1194
1195 if(isalnum_(c));
1196 else if(c == '(' || c == ' ' || c == '\n')
1197 {
1198 end = ptr;
1199 break;
1200 }
1201 else break;
1202
1203 ptr++;
1204 }
1205
1206 if(end > start)
1207 {
1208 items = realloc(items, (*size + 1) * Size);
1209
1210 items[*size].pos = start;
1211 items[*size].size = (s32)(end - start);
1212
1213 (*size)++;
1214 }
1215 }
1216 else break;
1217 }
1218
1219 return items;
1220}
1221
1222const tic_script_config MRubySyntaxConfig =
1223{
1224 .id = 11,
1225 .name = "ruby",
1226 .fileExtension = ".rb",
1227 .projectComment = "#",
1228 .init = initMRuby,
1229 .close = closeMRuby,
1230 .tick = callMRubyTick,
1231 .boot = callMRubyBoot,
1232
1233 .callback =
1234 {
1235 .scanline = callMRubyScanline,
1236 .border = callMRubyBorder,
1237 .menu = callMRubyMenu,
1238 },
1239
1240 .getOutline = getMRubyOutline,
1241 .eval = evalMRuby,
1242
1243 .blockCommentStart = "=begin",
1244 .blockCommentEnd = "=end",
1245 .blockCommentStart2 = NULL,
1246 .blockCommentEnd2 = NULL,
1247 .singleComment = "#",
1248 .blockStringStart = NULL,
1249 .blockStringEnd = NULL,
1250 .blockEnd = "end",
1251
1252 .keywords = MRubyKeywords,
1253 .keywordsCount = COUNT_OF(MRubyKeywords),
1254};
1255
1256#endif /* defined(TIC_BUILD_WITH_MRUBY) */
1257