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