mruby/cのStringを無効化した状態でUART
mruby/cでStringを無効化することができるようになった。
私が最近いじっているch32funを用いたCH32X035用の環境では、Stringが無いとそのままではI2CやSPIは動かなかったが、
出力はmake_output_bufferでStringを使わないようにして、
入力は文字列を整数の配列に変換することにより、動くようになった。
しかし、入出力ともにStringであるUARTは動かせていなかった。
APIのガイドラインとは異なる仕様になるが、UARTの入出力もI2CやSPIと同様の処理をすれば、String無しでもUARTを使えるようになるはずである。
今回はそのやり方について考えてみたい。
I2CやSPIでは、マイコンはセンサーなどのICと通信し、やり取りされるデータは数値であることが多い。
一方UARTでは、通信相手はPCや別のマイコンで、文字列をやり取りすることも多い。
それゆえに、UARTの入出力でStringを使うようになっているのだろう。
Stringを無効にした場合には、当然Stringは使えないが、Symbolを使えば限定的ではあるが文字列を扱うことができる。
そこで、
make_output_bufferでSymbolも使えるようにして、それをUARTの出力用に使うと良いと考えた。
UARTで出力するときには、通常はStringが引数として与えられるが、make_output_bufferはStringも受け付けることができるので、UARTでも問題無く動作する。
Stringを無効にしたときも考慮して、数値や数値の配列に加えて、Symbolを使って、出力すべきデータを指定するのである。
入力では、それをSymbolに変換することは可能であるが、mruby/cのSymbolには、GCが働かないようなので、様々なデータを受け付けたてSymbolに変換すると、メモリが圧迫されてしまう。
I2CやSPIと同様に、入力されたデータは数値の配列にするしか無いだろう。
Stringを無効にした
このような限定的な仕様にしたときに、UARTが何の程度の実用性があるかを、
例えば標準入出力とUARTのコンバーターが作れるかを考えてみよう。
標準入力は、getbyteで数値として受け取るので、それを数値としてUARTで出力すれば良い。
UARTからの入力は、数値の配列となるが、数値はprintfで%cを指定すれば、文字として標準出力に出すことができる。
つまり、Stringが無くても、コンバーターが作れることが分かる。
mruby/cの
コンパイルサイズを小さくするには、使わないAPIを組み込まないという方法もあるが、様々な状況に対応して、ソースを書き換えるのは面倒である。
それに対して、FloatやStringを無効にするのは、vm_config.hの中の指定を変更するだけで良い。
CH32X035用の環境では、Floatをすでに無効にしていたが、
さらにStringを無効にすると約10kバイト小さくなり、
String以外のmrblibを組み込んでも、
flashに10k以上の余裕ができる。
それだけあれば、かなり複雑なrubyのプログラムも組み込むことができる。
Stringを使って手軽に小さなプログラムを使う場合と、Stringを使わずに比較的大きなプログラムを作る場合用に、二種類のbinaryを用意しておけば、本体をコンパイルする必要はなく、mrbcだけ作って、binaryと結合して書き込めば良いので、便利だと思う。
mruby/cでStringを無効化
mruby/cをコンパイルしたときのサイズを小さくするには、いくつかの方法がある。 その中で、mruby/cの設計段階から想定されている方法が、FloatまたはStringを無効にする方法である。 FloatやStringを無効にするオプションの指定は、vm_config.hにで定義されているだが、3.4.1や4.0.0などのリリースでは、そのままでは動かない。 Floatについては自分でファイルを変更して対応していたが、Stringについてはどこをいじるべきか分らずに、放置していた。 しかし、github上ではつい最近これらの問題に対する対策がされたようである。 そこで、それを参考にしてmruby/cをStringを削除して使ってみた。
検証は、最近いじっているch32fun上のCH32X035用の環境で行った。 Taskは消しているので、mruby/cのそれ以外の部分を、Stringを無効にしても大丈夫なように修正するシェルスクリプトは以下のようになった。
awk '/MRBC_CLASS\(String/{$0="#if MRBC_USE_STRING\n"$0"\n#else\n 0,\n#endif"}1' class.c >temp.tmp && mv temp.tmp class.c cat c_object.c |awk '/void mrbc_object_inspect/{$0="#if MRBC_USE_STRING\n"$0;a=1}a&&/^}/{$0=$0"\n#endif";a=0}1' |awk '/static int set_sym_name_by_id/{$0="#if MRBC_USE_STRING\n"$0;a=1}a&&/^}/{$0=$0"\n#endif";a=0}1' >temp.tmp && mv temp.tmp c_object.c cat c_object.h |awk '/void mrbc_object_inspect/{$0="#if MRBC_USE_STRING\n"$0"\n#endif"}1' >temp.tmp && mv temp.tmp c_object.h cat error.c |awk '/static void c_exception_message/{$0="#if MRBC_USE_STRING\n"$0;a=1}a&&/^}/{$0=$0"\n#endif";a=0}1' |awk '/TT_STRING/{$0="#if MRBC_USE_STRING\n"$0;a=1}a&&/}/{$0=$0"\n#endif";a=0;b=1}b&&/}$/{$0="#if MRBC_USE_STRING\n"$0"\n#endif";b=0}1' |awk '/c_exception_message /{$0="#if MRBC_USE_STRING\n"$0"\n#endif"}1' >temp.tmp && mv temp.tmp error.c cat _autogen_class_exception.h |awk '/c_exception_message/{$0="#if MRBC_USE_STRING\n"$0"\n#endif"}1' >temp.
mruby/c 4.0.0のリリース
mruby/c 4.0.0がリリースされていたので、3.4.1との違いをざっと調べてみた。
機能の点では、mrbcで生成されるバイトコードの対応するversionが変わったのが最も大きな違いだが、いくつかのmethodが追加されている。
機能とはあまり関係ないところでは、struct VMがmrbc_vmに、halがmrbc_halに名称が変わったり、小数への変換に使われていたatofが、floatかdoubleによってstrtofかstrtodに変更になったりしていた。
最近はmruby/cの3.4.1をch32funでコンパイルしていたが、それを多少修正して、4.0.0をコンパイルしてみた。
Floatは使えなくして、Taskとmrblibも除いた状態で比較して、1.6k大きくなっていた。
新しいmethodなどが増えると、どうしても大きくなってしまうのは仕方無いことではあるが、サイズはあまり増加しないで欲しいものである。
先日のブログで、console.cなどのファイルを修正すると、約0.4k減ることを紹介したが、これを適応すると、59kとなった。
ch32x035だとユーザーが使えるのは3kということになる。
このくらいあれば、なんとか使えるレベルかな。
でも、さらにmethodが増えると、容量が厳しくなるだろう。
vm_config.hの中には、FloatやStringをoffにする指定があるのだが、そのままではコンパイルできなかった。
4.0.0でも、同じ状況ではあったが、github上では、これらに対する修正が加わったようなので、次のリリースではそのままでFloatやStringをoffにできるようになるだろう。
Stringをoffにすると、大幅にサイズが小さくなるだろうから、flashの容量が厳しいときには、そうして使うと良いだろう。
mruby/cのprintfの問題点
mruby/cをいじっていて気が付いたのだが、 そのprintfには二つの問題点があるように思われる。 その一つは、C言語からはprintf("%o",16)が使えないことである。 C言語からは八進数は使っていないので、加えてないのかも知れないが、二進数も使っていないのに、コードには書かれているので、加え忘れなのではないかと思う。 もう一つは、例えばprintf("%04d",-3)とすると、-003ではなく、00-3となるということである。 前者が正しいのだが、整数を文字列に変換してから、長さを調整しているので、このようになってしまうようだ。 これらを修正してみたので、ここでその解説をする。
mruby/cでは、printfについての関数はconsole.cで定義されている。 その最初の関数で"%d"などのフォーマットの文字によって、分岐する部分があるのだが、 ここに以下の記述を加えると、最初の問題は解決する。
case 'o': ret = mrbc_printf_bit( pf, va_arg(*ap, unsigned int), 3); break; しかし、bとoとxで三回も同じ関数が呼び出されていて、効率が悪く、これを加えると、私の環境でコンパイルすると12バイト大きくなった。 そこで、これらをまとめて、以下のように一つにすると、最初より8バイト小さくなって、八進数にも対応できるようになる。
case 'b': case 'B': case 'o': case 'x': case 'X': ret = mrbc_printf_bit( pf, va_arg(*ap, unsigned int), pf->fmt.type*9/2%8); // 1,3,4 for b/B,o,x/X break; 他にも、以下のような数式を考えたのだが、括弧が無くて読み易いので上のコードを採用した。
(pf->fmt.type&89)%7 (pf->fmt.type&0xdf)/7-8 ちなみに、c_object.cには、rubyのprintf用のc_object_sprintfという関数があって、その中では八進数に対応するコードはあるが、これを同様にまとめるように変更すると、70バイト程小さくなった。 これらの似た処理をうまく共通化できたら、さらに効率が良いのだけど。 しかし、C言語から呼び出す関数とruby用の関数では、型のチェックなどの処理の違いがあり、完全な共通化が難しいようである。
次の問題を解消するには、console.c中のmrbc_printf_int中の処理を変更する必要がある。 その処理の仕方を見てみると、 整数を文字列に変換して、別に長さを調整する文字列を用意して、そこに符号と精度の調整の0を追加して、整数の文字列を追加するという過程を取っている。 0で長さを調整した後で、符号が加わるのが問題なのである。 できるだけ変更が少なくて、思うような挙動をするにはどうしたら良いかを考えたが、0で長さを調整する場合には、精度を変更してしまえば良いということを思い付いた。 そうするには、pad_widthを定義しているところの次に、以下の行を加えると、ほぼ予定通りの挙動をするようになる。
if(pf->fmt.flag_zero && pf->fmt.precision<pad_width) pf->fmt.precision=pad_width; 長さと精度の両方を指定したときの挙動は、rubyとは若干違うかも知れないが、大きな問題では無いだろう。 しかし、この変更で、24バイトも大きくなってしまった。
さらにconsole.cの中を眺めていたら、以下の記述に目が止まった。
buf[2] = "0123456789ABCDEF"[uch >> 4]; buf[3] = "
nostdlibでmruby/c
ch32funでは、コンパイル時にnostdlibと指定されている。 これは、サイズが大きな標準ライブラリを使わずに、マイコンでも動くような小さなバイナリにするためである。 その代わりに、同様の役割をする自前の関数を作っている。 しかしmruby/cでは、nostdlibの状態では使えない関数をいくつか使っており、その中でch32funが代替の関数を用意していないものを三つ使っているようである。 その三つとは、atol, atof, strpbrkである。
atolは、文字列を整数に変換するものだが、to_i用に定義されているmrbc_atoiで底を10にしたらほぼ同じ挙動をするので、これで置き換えることができる。 残りの二つの関数は、Floatを使う時に必要になるものである。 atofは文字列をFloatに変換するc_string_to_fで使われている。 これは、新たに自分で書かないといけない。
strpbrkはto_fで使われている。 1e1や10.0はFloatだけど、10は整数と判断されてしまうので、 Floatを文字列に変換したときに10では無く10.0になるようにするために、 strpbrk(buf,".e")として、’.‘または’e’を検索している。 それなら、strpbrkを使わずに、 以下のようにしても良いので、value.cの中身を書き換えた。
strchr(buf, '.') || strchr(buf, 'e') つまり、あとはatofだけを何とかすれば、nostdlibでもコンパイルできるようになる。 通常のC言語のatofはかなり大きくなるらしいので、できるだけコンパクトになるようにプログラムをしてみた。
double atof(const char *s) { int digit=0, sign=1; unsigned count=0, point=0; double res=0, factor=1; for(;*s == ' ';s++); if (*s == '-' || *s == '+') sign = (*s++ & 2)-1; //+:0x2b, -:0x2d for (;*s;s++) { unsigned v = *s-'0'; if (v <= 9) { if (point) digit--; if(count>>4){ digit++; }else{ res = res * 10 + v; if(count || v) count++; } } else { if (*s == '.
ch32funのmruby/cでCH32X035のmrblib
CH32X035用に開発してきたch32funを用いたmruby/cの環境では、FloatとmrblibとTaskを無効にする必要はあるが、 mruby/cの標準的なAPIであるGPIO,ADC,PWM,I2C,SPI,UARTに加えて、標準入出力としてUSB CDCをを組み込んで、flashに4k以上の余裕がある。 ユーザー用のmrbの容量をどの程度確保するかにもよるが、 この余った容量を有効に活用する方法について考えてみた。
その一つの使い方が、無効にしていた機能の一部を有効にするということだろう。 Taskを有効にすることは出来るが、個人的には不要だと思う。 Floatは大き過ぎて、すべてを有効にすることはできない。 一部だけ組み込むことはできるかも知れないが、その取捨選択やmruby/cのソースを変更するのが大変だと予想される。 mrblibを組み込むと、4.5k以上消費するので、これもすべて組み込むことは不可能である。 しかし、mrblibはrubyで書かれているので、その一部を選んで組み込むことも比較的容易である。 mrblibでは、loopやeachやtimesなどの、いくつかの重要なmethodが定義されている。 これらの重要なmethodだけでも組み込めれば有用だろう。
そのためにはmrblibのファイルが必要になるので、 mrubycフォルダにmruby/cのmrblibフォルダをコピーする。 いくつかのファイルから構成されているが、enum.rbが最初に処理され、それ以外はアルファベット順になっていることが、Makefileを見ると分かる。 後で示す方法でこれらを組み込んだときのmrbのサイズの増加は以下の表のようになった。
file mrb size enum.rb 303 array.rb 2135 hash.rb 202 numeric.rb 285 object.rb 102 range.rb 178 string.rb 1216 まず、arrayとstringが大きく、それ以外はそれほど大きく無いことが分かる。 それぞれのファイルで定義されているmethodは、2026/5/10のブログで取り上げている。 string.rbのmethodは、使わなくても困らないものばかりなので、組み込まなくても良いだろう。 一方、array.rbでは、eachなどの重要なmehodがあるし、Enumerableも組み込んでいるので、methodを取捨選択して組み込むと有用だろう。 array.rbを編集して、methodを消すのは難しく無い。 フォルダの構造にもよるが、以下のようなコマンドで、mrblibの一部とuser.rbを処理したtemp.mrbができる。
mrbc --remove-lv -otemp.mrb ../mrubyc/mrblib/enum.rb ../mrubyc/mrblib/array.rb ../mrubyc/mrblib/hash.rb ../mrubyc/mrblib/numeric.rb ../mrubyc/mrblib/object.rb ../mrubyc/mrblib/range.rb user.rb そして、main.binとtemp.mrbをcatでくっつけて、wchispで書き込めば良い。 array.rbのeach以外のmethodを消して、string.rb以外を処理すると、約1.3kとなった。 すると、ユーザー用に2.7k以上残る。 単純なプログラムなら、このくらいの容量で十分だろう。
極端な例だが、 object.rbで定義されているloopを使ったtemp.rbを実行するためには、
mrbc --remove-lv -otemp.mrb ../mrubyc/mrblib/object.rb temp.rb cat main.bin temp.mrb >temp.bin sudo ./wchisp flash temp.bin とすれば良いのである。 このときのtemp.
ch32funのmruby/cでCH32X035用にインストール
CH32X035をmruby/cから使うためのch32funを利用したプログラムは、ほぼ完成に近付いたと思っている。 その情報は主に今月のブログに書かれているが、ファイルの数も増えて来たし、複雑になっているように感じる。 私でも、このブログの内容を元に、それを再現するのは時間がかかりそうだ。 そこで、この環境を簡単に構築するシェルスクリプトを書いてみた。
まず、事前にrubyをインストールしておく必要がある。 また、wgetとsedやawkも使っているので、これらも同様であるが、通常のlinuxではすでに使える様になっているはずである。 適当なフォルダを作って、以下のスクリプトを実行すると、必要なファイルを取って来て、適切に修正するようになっている。
# download wget https://github.com/mrubyc/mrubyc/archive/refs/tags/release3.4.1.zip wget https://github.com/cnlohr/ch32fun/archive/refs/heads/master.zip wget https://github.com/ch32-rs/wchisp/releases/download/nightly/wchisp-linux-x64.tar.gz # unzip and copy mkdir mrubyc_ch32fun unzip master.zip mv ch32fun-master/ch32fun/ mrubyc_ch32fun/ mv ch32fun-master/extralibs/ mrubyc_ch32fun/ mkdir mrubyc_ch32fun/mrubyc unzip release3.4.1.zip mv mrubyc-release3.4.1/src/ mrubyc_ch32fun/mrubyc/ mv mrubyc-release3.4.1/support/ mrubyc_ch32fun/mrubyc/ mv mrubyc-release3.4.1/mrblib/ mrubyc_ch32fun/mrubyc/ cat mrubyc-release3.4.1/hal/minimal.h | awk '/define MRBC_SRC/{$0=$0"\n#include \"ch32fun.h\""}1' |sed 's/delay/Delay_Ms/' > mrubyc_ch32fun/mrubyc/src/hal.h tar xzvf wchisp-linux-x64.tar.gz # usb awk '/void HandleDataOut/{a=1}a&&/^}/{$0="\tpoll_input();\n"$0;a=0}/USBPRINTF/&&!u{$0="0 //"$0;u=1}1' mrubyc_ch32fun/extralibs/fsusb.c >temp.tmp && mv temp.tmp mrubyc_ch32fun/extralibs/fsusb.c # modify files cd mrubyc_ch32fun/mrubyc/src make autogen # require ruby # remove task rm rrt0.
ch32funのmruby/cでCH32X035用のGPIOを拡張
このところ、CH32X035をmruby/cから使うために、ch32funを利用したプログラムを作ってきた。 ほぼ完成したと思っていたが、16番以降のGIPOが使えないことを思い出した。 これはch32funのarduino-likeなインターフェースの仕様のためなのだが、それを拡張する形で組み込むことが出来たので、それについて説明する。
以前にも説明したが、ch32funでは、ピンを数字で管理しており、 PAはピン番号そのまま、PBはピン番号に16を足した数字、PCは32を足した数字となっている。 これは、CH32Vでは各ポートには8ピンまたは16ピンが割り当てられているために、そのような割り当てが自然だし、上位bitを取り出すとポートの情報が得られるという利便性があるためであろう。 しかし、CH32X035では、一つのポートに最大で24ピンが割り振られている。 そして、書き込みやモードの設定に使うレジスタが、0-15と16以降で不規則に離れているために、単純な形で扱えるのが16ピンまでになっている。 そのため、0-15まではマクロを使って簡単に扱えるが、16以降はレジスタを直接操作して使うようになっている。 しかし、それではmruby/cから使えるようにするのが困難である。 そこで、ch32funのマクロを拡張して、すべてのピンの制御ができるようにして、それをmruby/cから呼び出して、GPIOを扱うことにした。
そのためには、16以降のpinに数字を割り振る必要がある。 これまでの0-47を保って、それ以降に16以降のピンを割り当てるのが他の部分に影響が無くて安全だとは思ったが、pin番号が明らさまに関連するのは、GPIO以外ではPWMぐらいなので、扱い易いように新たに数字をつけ直した。 数字に空きがあると、無駄が増えると思ったので、それぞれのポートの24本を順に並べて、0から順番に数字を割り当てることにした。 つまり、PAは番号そのまま、PBはピン番号に24を足して、PCは48を足すのである。 すると、ピンnに対して、ポートはn/24で、ピン番号はn%24で求められる。 bit演算では無いので、多少は効率は落ちるかも知れないけど。 そして、数値によって適切なアドレスのレジスタにアクセスするようにマクロを書き換えた。
定数などもch32funに合わせて変更して、出来上がったのが以下のプログラムである。 PWMなどでも使う可能性があるので、マクロの定義はmrbc_gpio.hに入れることにした。
#ifndef _MRBC_GPIO_H #define _MRBC_GPIO_H #include "ch32fun.h" #include "mrubyc.h" //PA 0-23 0-23 //PB 0-23 24-47 //PC 0-23 48-71 #define GpioOf2( pin ) ((GPIO_TypeDef *)(GPIOA_BASE + 0x400 * ((pin)/24))) #define funDigitalWrite2( pin, value ) do{ *((&GpioOf2(pin)->BSHR)+((pin%24)>>4<<2)) = 1<<(((value)?0:16)+((pin%24)&0xf));}while(0) #define funDigitalRead2( pin ) ((int)((GpioOf2(pin)->INDR >> ((pin)%24)) & 1)) #define funPinMode2( pin, mode) do{ volatile uint32_t *p = (&GpioOf2(pin)->CFGLR)+((pin%24)>>3)+(((pin%24)>>4)*5); *p&=~(0xf<<(((pin)&0x7)<<2)); *p|=((mode)<<(((pin)&0x7)<<2)); }while(0) typedef struct GPIO_HANDLE { uint8_t pin_num; } GPIO_HANDLE; void mrbc_init_class_digital(void); #endif マクロの定義のdo{}while(0)は無駄に思えるが、マクロの呼ばれ方によって、意図しない動作をしないようにするおまじないである。
ch32funのmruby/cでTaskの削除
最近、安価なCH32X035をmruby/cから使うための環境の開発を行っているが、 このマイコンにはflashが62kしか無いので、ある程度機能を制限する必要がある。 まず、Floatの削除は必須である。 mrbとして組み込んでいたmrblibも、組み込まないようにすることで、使えるmethodが減ってしまうが、容量を減らすことができる。 この状態で、flashは1k程余るのだが、ユーザーのプログラムが少し長くなると、足り無くなるだろう。
このような単純なマイコンでマルチタスクをすることは無いので、Taskも削ることができる。 mrubyc_arduinoについて2026/4/20に書いたようにして、 さらにTaskを削除すると、約3k小さくなり、合計で約4kのflashがあれば、通常の使用には十分であろう。 以前のTaskの削除の仕方は、rrt0.hやrrt0.cの中身を書き換えないといけなかったが、もう少し単純な方法を発見したので、それを紹介しよう。
まず、rrt0.hとrrt0.cと_autogen_class_rrt0.hは、完全に消してしまってよい。 ただし、mrubyc.hでrrt0.hをincludeしているので、その行をコメントアウトする。 そして、メインのコードで、rrt0.cの中でされていたメモリ確保やsleepなどの定義をすれば、Taskを削除できる。 メインのコードが少し長くなるが、この方が単純だろう。 その際のmain.cは、以下のようになる。
#include "ch32fun.h" #include <stdio.h> #include "fsusb.h" #include "mrubyc.h" #include "mrbc_gpio.h" #include "mrbc_adc.h" #include "mrbc_pwm.h" #include "mrbc_i2c.h" #include "mrbc_spi.h" #include "mrbc_uart.h" #include "mrbc_get.h" #define MEMORY_SIZE (1024*10) static uint8_t memory_pool[MEMORY_SIZE]; #define FLASH_CODE (0x8000000+59152) #define RX_BUFFER_SIZE 64 volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; volatile uint8_t rx_head = 0; volatile uint8_t rx_tail = 0; void rx_buffer_push(uint8_t c) { uint8_t next = (rx_head + 1) & (RX_BUFFER_SIZE - 1); if (next !
ch32funのmruby/cのhalと標準入出力
mrubyc_arduinoでは、hal_writeなどは、メインのスケッチの中で定義されている。 ch32funを使ったmruby/cにおいても、なんとなくその流儀を踏襲していたが、ファイルの構造を眺めていたら、これらはhal.cの中で定義すべきものであることに気が付いた。 そこで、ch32fun用のhal.cを作ってみたが、こんな感じになった。
#include "hal.h" #include "ch32fun.h" int hal_write(int fd, const void *buf, int nbytes) { _write(0, (char*)buf, nbytes); return nbytes; } int hal_flush(int fd) { return 0; } void hal_abort(const char *s) {} するとこれらをメインのコードに書く必要が無くなる。
hal_writeはmruby/cの標準出力に対応するが、 2026/4/25のブログに書いたように、 私は標準入力も使いたいので、mrbc_get.cなどを作ってhal_readという関数を介してgetbyte,getc,getsを使えるようにしている。 以前はhal.hの中でhal_readの宣言を行っていたが、mruby/cのファイルをできるだけ変更するべきでは無いと考えて、mrbc_get.hに移動した。
#ifndef _MRBC_GET_H #define _MRBC_GET_H #include "mrubyc.h" unsigned char hal_read(int fd); void mrbc_init_class_get(void); #endif また、mrbc_get.cにおいては、getbyteはC言語で定義したが、getcとgetsはrubyで書いてmrbに変換して組み込んでいた。 しかし、コンパイルしたときのサイズを少しでも小さくするために、これらもC言語で書いてみた。 getsは動的なバッファを確保すると複雑になるので、一行は127文字以下とした。
#include "mrbc_get.h" static void c_object_getbyte(struct VM *vm, mrbc_value v[], int argc) { uint8_t ret=hal_read(0); SET_INT_RETURN( ret ); } static void c_object_getc(struct VM *vm, mrbc_value v[], int argc) { char buf[2] = {0,0}; buf[0]=hal_read(0); mrbc_value value = mrbc_string_new_cstr(vm, buf); SET_RETURN(value); } static void c_object_gets(struct VM *vm, mrbc_value v[], int argc){ int i; char buf[128]; for(i=0;i<127;){ buf[i]=hal_read(0); if(buf[i++]=='\n') break; } buf[i]=0; mrbc_value value = mrbc_string_new_cstr(vm, buf); SET_RETURN(value); } void mrbc_init_class_get(void){ mrbc_define_method(0, 0, "