なんだか雲行きの怪しい雑記帖

ぐだぐだ日記とメモと,あと不定期更新

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

【HSP】「mist」の文法対応、構文拡張まとめ

この記事は?

拙作HSP用の動的実行プラグイン「mist」の話。

【HSP】不穏な動的実行プラグイン「mist」の記事ではHSPからmistの主に互換性部分について触れていますが、mistの実装自体互換性らへんは(プリプロセスを除いて)ある程度落ち着いてきていることもあり、マルチスレッド対応など動作拡張以外にも少しずつ構文拡張にも手をだしていたりします。

私はHP持っていないのでmistを実際にリリースする時は上の記事内容を修正していたりするのですが、そちらに互換性の話と拡張文法の話を一緒くたにすると流石に内容が膨れ上がってしまい、(読む人と、主に私の脳内が)収集がつかなくなってしまう気がしたので、内容を分割して構文拡張を主眼にした記事は別に作ることにしました。
つまりこの記事がそれです。

もっと具体的には何書くの?

この記事では構文拡張の拡張内容と、そもそもmistでは文法的にどこまで対応してないのかについてまとめていこうと思います。
先の記事とは内容が重複する箇所もあるかと思いますが、あちらは「HSPからmistを使うまでの導入」という流れを強く意識しているのに対し、本記事の方では文法面を主軸として「HSP本家と何が違うのか」について整理していこうかと思います。

主に書く内容については次あたりを予定。
 ・キーワード
 ・演算子の優先順位(結合順位)
 ・構文拡張

対応早見表

雑な機能表ですが、一応。

機能HSPでのサポートmistでのサポート
特殊マクロ×(対応予定なし)
COM利用×(対応予定なし)
while~wend、for~next、do~until×(対応予定なし)
三項演算子×0x27000~
明示的な改行無視(¥、_)×0x27000~
自動で改行無視×括弧のアンバランス:0x27000~
文末が演算子:0x27020~
短絡評価×0x27000~
ローカル変数定義×関数スコープ:0x27020~
論理否定演算子×結構最初から
ビット反転演算子×結構最初から
マルチスレッド×0x21xxx~
プリプロセッサ限定的#if:0x25000~
限定的#define:0x25000~
#const、#enum:0x25000~
#include、#addition:×
文字列への式埋め込み×0x27030~
自己代入の省略
(一行にa+2とかだけ書くような文法)
×(対応予定なし)


キーワード

キーワードはこの場合 if とか else とか repeat とか、文法上で役割があり識別子として用いることができない単語を指します。

HSP本家ってどこまでがキーワードなんだろうか、というところはあるんですが、OpenHSPのhspcmpの中身を見て構文解析時に特殊な処理をしているものを抜き出しています。(HSPではレクサは「なんか識別子っぽいもの」としてしか全て見ていないようで、パーサでトークンの内容を見て分岐しているようです)

HSPでのキーワードは恐らく次のような感じ。
if、else
break、continue、repeat、loop、foreach
stop、on、await
dim、sdim、dimtype、dup、dupptr

awaitは構文上の規則というわけではなく、単純に最適化のためにとっているだけなので、正確にはキーワードじゃないかも。

さてmistでのキーワード、mistではレクサで特殊扱いして抜き出しているものがキーワードとなります。本家に比べるとかなり多いです。
global、ctype
#deffunc、#defcfunc
nil、any、int、double、str、label、var、array、local
#module、#global、#modfunc、#modcfunc、#modinit、#modterm
if、then、else
switch、case、swbreak、default、swend
repeat、loop、continue、break
goto、gosub、return、resume、yield

これだけ多いのはmistは動的にスクリプトを追加できる関係で、本来プリプロセッサであるべきところがプリプロセッサじゃなくて文法になっていたりするのもありますが…。
また、HSP本家ではswitchはマクロで実現されているいますが、mistでは構文に取り込まれていますので、キーワードとして定義されています。
thenは、後述しますが三項演算子に利用しています。
resume、yieldは本家が使ってないのでコマンド化するか悩んでいるところですが、とりあえずキーワード化しています。

mistでキーワード扱いしているものについては全て変数名などに使えませんので気を付けてください。
本家では global や ctype といったものはプリプロセスの特定のコンテキストでしかキーワード扱いされないものなので変数名などにも使用できますが、mistでは文脈関係なくキーワード扱いなので予期されないトークンとして構文エラーになります。

なお、mistで拡張キーワードが有効だと次のものもキーワードとして読まれます。(ちなみにデフォルトで有効です)
class、endclass、def、enddef、self、super、public、private、lambda
not、and、or、xor
lock、let

まだ使っていないものもあるので、キーワードというか予約語みたいな立ち位置のものもあったりしますが…。

演算子の優先順位

上から下に向かって、優先順位が下がっていきます。

HSP本家

単項演算:-
乗算、除算、剰余:*、/、¥
加減算:+、-
ビットシフト演算:<<、>>
比較演算:>、<、>=、<=、==、!=
ビット演算:&、|、^


こうしてみるとビット演算が一番優先度低いので、論理演算みたいに使えるものであることが分かる。

mistでの演算子の優先順位

後置修飾:->、.
単項演算:-、~、!
乗算、除算、剰余:*、/、¥(または%)
加減算:+、-
ビットシフト演算:<<、>>、>>>
比較演算:>、<、>=、<=
等価、不等価演算:==、!=
ビット積:&
ビット排他和:^
ビット和:|
論理積:&&
論理和:||
三項演算:if-then-else


見ていただける通りビット演算同士に明確な優先順位があったりするなど、微妙に違うところがあります。

構文拡張

恐らく本記事のメイン。
対応していない文法と、本家から拡張されている構文について述べていきます。

対応していない文法


特殊マクロ

本家の#define中に使用できる %s や %t といったスタックに値を積んで他のマクロ間で値を使い回しできる機能で、特殊マクロと言われています。
mistではサポートしていませんし、将来的にもサポート予定は今のところありません。

while~wendなどの制御構文

本家では特殊マクロを使って実装されているため、特殊マクロを実装していないmistでも…、という理屈ではありませんが、mistではwhile~wend、do~untile、for~nextといった構文もサポートしていません。
特殊マクロと同じく、将来的にもサポート予定は今のところありません。
ただ、サポートしない理由も特に大きなものがないので、何かプラスの理由が見つかったら実装するかも、ぐらいのノリです。

COM利用

newcom、delcom、comres等のCOMを利用した機能、および文法(アロー演算子など)についてはサポートしていません。
将来的なサポート予定はありません。

コンパイルオプションなど一部のプリプロセッサ

#cmpoptや#pack、#runtimeなど、mistはコンパイラではなくランタイムも動的に切り替えられないため、これらの機能はサポートされません。
将来的なサポート予定はありません。

拡張されている文法


三項演算子

演算子の優先順位でも書きましたがmistでは三項演算子を追加しています。
    form = "return ( if 1<3 then 4.11 else 2 )"
mes ""+form+" =\t"+mstload( form )

結果:
return ( if 1<3 then 4.11 else 2 ) = 4.110000

三項演算子は if (分岐を判断する式) then (真の場合) else (偽の場合) という構文です。
優先順位は最も低いので、他の演算子と併用する際はわざわざ括弧をつける必要がないです。
それぞれの値の中にも三項演算子を使うことはできるので、入れ子にすることも可能です。

改行を自動で無視

HSPと違い明らかに文の途中であることが分かる文脈では改行が無視されます。
    mes "()=>\t"+mstload({"
if ( 1 == 0
|| 2 < 10 ) {// ↑は)がないのでこの行までくる
return 72.2
}
return 89.9
"})

結果:
()=>72.200000


三項演算子と組み合わせると分かり易く書けることが多そうです。
    mes "BBranch =\t"+mstload( {"
v = 2
return ( if v<5
then if v<2
then "v<2"
else "2<=v<5"
else
if v<7
then "5<=v<7"
else "7<=v"
)"} )

結果:
BBranch = 2<=v<5


また、文末に演算子がきている場合も自動で改行が無視されます。
    mes "lineEnd(operator)=\t"+mstload({"
return 3 *
2 +
1
"})

結果:
lineEnd(operator)= 7

こっちの方が事故が多そうですが、同時に利便性も上がるような…微妙なラインですね…。

改行の明示的無視

改行前に「¥」か「_」を置くと改行が無視されます。
これはdefineのコンテキストに限らず有効です。
    mes "lineContinuous=\t"+mstload({"
return 1 + \\
2 _
+ 3
"})

結果:
lineContinuous= 6

¥はC系列、_はVB系列らしいので好みの方をどうぞ。
(ただし、トークン区切りでしか認識しないので注意してください。)

短絡評価演算子

&&と||の短絡評価を有効にできます、デフォルトでは挙動が変わってしまうので無効です。
        // 挙動が変わるのでオプションで有効化
mstEnvConf MIST_ENVCONF_EXT_SHORTCIRCUIT_ANDOR, 1

form = "return 38 && 28"// and
mes "shortCircuit : "+form+" :\t"+mstload(form)

form = "return 38 || 28"// or
mes "shortCircuit : "+form+" :\t"+mstload(form)

結果:
shortCircuit : return 38 && 28 : 28
shortCircuit : return 38 || 28 : 38

Rubyなどと同じく、途中で条件が確定した時点でそのときの項を返します。
(&&では左の項がTrueだったら右を、Falseだったら左を返す。
 ||は逆に左の項がTrueだったら左を、Falseだったら右を返す演算です。)

短絡評価なので、右の項を評価する必要がない場合は評価されません。(そういう意味では上の例は最適な例ではないですね…)
左の項は基本的には必ず評価されます。
短絡評価の自己代入演算子は今のところないです。

単項演算子:ビット反転と論理否定

かなり初期のバージョンから入っている機能ですが、ビット反転と論理否定の単項演算子が追加されています。
        // ビット反転
form = "return ~20"
mes ""+form+" :\t"+mstload(form)

// 論理反転
form = "return !20"
mes ""+form+" :\t"+mstload(form)

結果:
return ~20 : -21
return !20 : 0

ビット反転するとHSPではintなので2の補数表現ででてくる値になります。(つまり符号を反転して1引いた値)
論理否定はそのままです、あまりマニュアル中に明記されていませんが、mistはtrueを1、falseを0に変換します。

ローカル変数の定義

構文的にはブロック内でローカル変数が宣言できるものを目指しているものです。
が、現状はブロックがそもそもHSPに存在しないため、関数の宣言時ではなく関数中でローカル引数(localの引数)が必要になったときにその場で書ける、というメリットしかないです。
        // let構文:ローカル変数:現状では後から関数にローカル引数を付け加えるだけ
mstcompile {"
#deffunc f int i
fu = i*3
let fv = i*2// 実質的には関数宣言時に local fv としても同じ挙動
return fv+fu
"}
mes "let : "+mstcall( "f", 4 )+" : fv="+mstget("fv")+" : fu="+mstget("fu")

結果:
let : 20 : fv= : fu=12

fuは実質グローバル変数に書き込んでいるのに対し、fvはletがついているので関数のローカル変数として処理されます。
let文がくるまでは同じ名前を隠蔽しないため、上記例だとletの前にfvに対して代入を行うと「fv@hsp」への代入と見做されます。(ローカル変数への代入にはなりません)
初期化子を伴わないlet文を書くこともできますが、その場合は初期値として0が代入された状態になっています。(後々NILに変更するかもなので、基本的には初期値を入れるのを推奨します)

関数のローカル変数、新しいの追加したときいちいち宣言行に戻らなくちゃいけないのが嫌だったので追加した、というのがもともとの理由。
これ単体だと今は大した機能じゃないんですが、ブロックやクロージャ(予定)である lambda(予約語)ともうまく絡めていければいいなぁという感じ。

文字列への式埋め込み

なんと形容するのが一番正しいのか分かりませんが、内容を見るとRubyとかJS(ES6)で実装されているものと同じだと思います、
Rubyでは特にこの機能にあたる単語が分からなかったんですが、ES6ではTemplateStringと言われている機能の一部ですね。
        // 文字列への式埋め込み

// 挙動が変わるのでオプションで有効化
mstEnvConf MIST_ENVCONF_EXT_STRING_EMBEDDED_EXP, 1

form = {"v = 20 : return "v+10 is #{v+10}" "}
mes ""+form+" => "+mstload(form)
// 内部的には return "v+10 is "+(v+10)+""として扱われる、糖衣構文

結果:
v = 20 : return "v+10 is #{v+10}" => v+10 is 30

「#{}」でくくった部分が実行時評価される式として埋め込まれる挙動です、ES6形式で「${}」と書いても同様です。
ただし、Rubyと違って「#varname」のようにはかけません、必ず「#{varname}」と書く必要があります。

コメントにもかいてありますが内部的にはただの糖衣構文です、レクサレベルで処理しているのでパーサで見つかるエラーの場合はかなりよく分からないエラー内容になります。(エラー内容はまだ整備中ですが)

関連記事
スポンサーサイト

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://fe0km.blog.fc2.com/tb.php/123-5f51dca5
この記事にトラックバックする(FC2ブログユーザー)

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。