プチコン4 まとめWiki

Toukou/PXLIB の変更点

パンくずリスト


#title(PXIILIB-0.3.1 パーサコンビネータライブラリ)

#region(目次)
目次
#contents
#endregion

// ↑↑↑↑ この行は削除しないでください。 ↑↑↑↑
// ←「//」で始まる行はコメントです。ページ本文には反映されません。

// --------------------------------------------------------------------------------------------
// ・以下に作品の情報を書きます。

2020年3月20日投稿
/ 2020年3月26日更新
/ 投稿者 : うつりきまぐれ
/ &tag(投稿プログラム,ツール,ライブラリ,うつりきまぐれ,構文解析,パーサ,パーサコンビネータ,オブジェクト,JSON);

// ・投稿年月日は、いま投稿しようとしている今日の日付を半角数字で記入してください。
//   投稿プログラムページの「投稿された順」で上位に表示されます。
// ・作品の公開キーをアップデートしたら、更新日を記入して、頭の「//」2文字を取り除くと
//   「アップデート順」で上位に表示されます。(ページの更新だけで更新日の記入をするのは禁止です)
// ・投稿者名は、投稿プログラム一覧に表示するために使用します。匿名(名無しさん)でも可能です
// ・&tag() 内にコンマ区切りで、作品に関するタグ(任意の単語)を並べると、
//   同じタグがついた作品を検索できます。
// --------------------------------------------------------------------------------------------

// --------------------------------------------------------------------------------------------
// ここから先に概要など、作品の説明を書きます。
// ・* や ** や ** ではじまると見出しになります。* が多いほど階層が深くなります。
// ・[#英数字] はページ内リンクに使用されるキーワードとなりますが、消してもかまいません。
//   消したりなかったりの場合は、ランダムな文字列が自動で付与されます。
//   逆に、ページ内に同じキーワードが複数ある場合、目次からのリンクが正常に動きません。
// --------------------------------------------------------------------------------------------




* これは何? [#Summary]

プチコン4上で文法を定義して構文解析するためのライブラリです。構文解析とはプログラムを先頭から読んで単語ごとに切り分け、それらを項や式、文、ブロックといったまとまりごとに構造的なデータ (構文木) に整理します。( 厳密には最初の単語ごとの分解処理は「字句解析」という構文解析の前の別のステップです。) 構文解析については説明が難しいのでまあググってもらえると助かります。

このライブラリが追加で使用するオブジェクトライブラリと JSON 解析・生成ライブラリ、および actorbug さんが製作した [[正規表現ライブラリ>3gou:Toukou/正規表現ライブラリ]] の移植版がひとつのファイルになっています。

このライブラリでは文法を定義するのにパーサコンビネータというものを使います。詳しい説明は%%ググれカス%% このライブラリを使おうと思っている皆さんは知っているだろうという前提で進めます。

サンプルプログラムとして四則演算と括弧が利用できる計算機を用意しています。また、ライブラリ内にもパーサコンビネータを実際に利用した JSON 解析ライブラリを内蔵しています ( 逆に生成もできます ) 。




* ライブラリの使用 [#UseLibrary]


** ライブラリを読み込む [#LoadLibrary]

構文解析ライブラリおよびオブジェクトライブラリの本体は ''PXII.LIB'' という名前です。以下のようにしてライブラリを読み込み、実行します。

#basic{{
EXEC "PXII.LIB",1
}}

別のプロジェクトから参照するときは &basic{EXEC}; がエラーになるので、代わりに &basic{LOAD}; でスロットに読み込んでから &basic{EXEC}; で実行してください。

#basic{{
LOAD "PXIILIB-0.3.0/PXII.LIB",1
EXEC 1
}}

ライブラリの初期化のための &basic{DEF}; や &basic{GOSUB}; の呼び出しは特に必要ありません。


** 文法を定義する [#DefineGrammar]

構文解析を行うより先に、次のように文法を定義してください。これは整数の四則演算の例です。符号はありません。……なお、実際に計算を行うには &basic{ACTP}; を使ってアクションというコールバック &basic{DEF}; も指定しなければならないため、これよりも少々複雑になります。

#basic{{
PXDEFPC @_SPACE,REGEXP("\s*")
PXDEFPC @EXPR,LISTP(@TERM,CHARP("+-"))
PXDEFPC @TERM,LISTP(@TERM,CHARP("*/"))
PXDEFPC @FACT,ORP(@REAL,SEQP("(",@EXPR,")"))
PXDEFPC @REAL,REGEXP("[0-9]+")
}}

用意されているパーサを組み合わせて、新たなパーサを作成します。パーサは通常の変数に代入して利用できますが、これでは上記のような再帰文法が定義できません。( Undefined variable となる) そのため、別の手段として [[&basic{PXDEFPC};>#PXDEFPC]] を用いて &basic{@}; からはじまる名前をつけます。この名前は上記の &basic{@EXPR}; のように再帰できます。( ただし自身の名前が含まれる再定義は現在のところできません ) またパーサコンビネータの実体はオブジェクトですが、代わりに名前を指定できるようになります。ただし &basic{@_SPACE}; という名前は、スキップパーサを示すために予約されており、ここに指定したものは [[&basic{PXPARSE};>#PXPARSE]] が呼び出されるたびにパースされ無視されます。

また、&basic{@}; から始まらない文字列を直接指定した場合は、自動的に [[&basic{TEXTP};>#TEXTP]] に変換されます。つまり、&basic{@}; から始まらない文字列を読み取る場合は [[&basic{TEXTP};>#TEXTP]] を省略できます。

用意されているすべてのパーサコンビネータの詳細は [[パーサコンビネータのリファレンス>#ParserCombinators]] を参照してください。


** 構文解析を行う [#Parse]

実際に構文解析を行うには次のようにします。

#basic(start=6){{
DIM SOURCE="11+(9-5)*7"
DIM RES=PXPARSE(SOURCE,0,@EXPR)
}}

第1引数に入力、第2引数に開始位置、第3引数にパーサコンビネータを指定します。結果はオブジェクトでまとめて返るため、情報を取得するには [[&basic{OBJGET};>#OBJGET]] などでキーから値を得る必要があります。オブジェクトの使い方については [[オブジェクトを扱う>#UseObjects]] を参照してください。

次のようにして、パースが成功したかどうかと次回の開始位置 ( 終端位置 + 1 ) および読み取った文字列を取得できます。パースが失敗した場合は、その時点での位置になります。

#basic(start=8){{
DIM SUCCESS=OBJGET(RES,"SUCCESS")
DIM EPOS=OBJGET(RES,"POS")
DIM BUFF=OBJGET(RES,"BUFF")
}}

また [[&basic{SEQP};>#SEQP]] や [[&basic{REPP};>#REPP]] など一部のパーサコンビネータは別のパーサコンビネータを引数にとるため、これらの結果を参照できるように、&basic{”SUCCESS”}; と &basic{”POS”}; および &basic{”BUFF”}; のほかにも追加の情報が入っています。たとえば [[&basic{SEQP};>#SEQP]] では、&basic{”SEQ0”}; に最初に指定したパーサコンビネータの結果が入ります。これもまたひとつの結果オブジェクトなので &basic{”POS”}; などのキーを持っています。


** パーサを拡張する ( v0.3.1 から ) [#ExtendParsers]

&basic{PXDEFPC}; で定義した名前は再定義できるので、このライブラリを用いて言語処理系などを作ると、外部からパーサを変更することができます。たいていは後に出る例のように、そのパーサ自身を含むように拡張することになります。

しかし、その場合には問題があります。たとえば &basic{@FACT}; という名前のパーサがあったとします。先ほどの計算機で変数も利用できるように、次のように'' &basic{@FACT}; を使って'' &basic{@FACT}; を再定義すると、パース時に Stack overflow エラーになることがあります。( 以下の例で &basic{@VAR}; は既に定義されているものとします )

#basic{{
PXDEFPC @FACT,ORP(@FACT,@VAR)
}}

これを回避するには、再定義する対象のパーサを [[&basic{EXTP};>#EXTP]] で包みます。

#basic{{
PXDEFPC @FACT,ORP(EXTP(@FACT),@VAR)
}}

バージョン v0.3.1 以上で利用できます。


** オブジェクトを扱う [#UseObjects]

この構文解析ライブラリでは、パーサコンビネータを保存したりパースの結果を返すのにオブジェクトが使われます。このオブジェクトは JavaScript のオブジェクトと同じようなものです。オブジェクトの実体は文字列配列ですが、値にキーという名前 ( 文字列の添え字のようなものです ) を付けて、あとからその名前で取得できるようになります。

オブジェクトには次の種類の値を格納することができます。

- デフォルト値
- 整数
- 実数
- 文字列
- 別のオブジェクト

オブジェクトライブラリを使った例です。

#basic{{
' オブジェクトを生成
VAR A=OBJECT()

' 数値や文字列を入れる
OBJSET A,"SCORE",80
OBJSET B,"COMMENT","おめでとう"

' 内容を表示
? "あなたの点数は";OBJGET(A,"SCORE")

' 別のオブジェクトを生成
VAR B=OBJECT()

' 同じように数値や文字列を入れる
OBJSET B,"NAME","山田"
OBJSET B,"AGE",22

' オブジェクトに別のオブジェクトを格納
OBJSET B,"RESULT",A

' ピリオドで階層を指定
? OBJGET(B,"RESULT.COMMENT");"!"

' こうすることもできる (こちらの方は遅い)
? OBJGET(OBJGET(B,"RESULT"),"COMMENT");"!"

' OBJSET にもピリオドを使える
OBJSET B,"RESULT.SCORE",0
}}

オブジェクトのキーを &basic{CHR$(&HFFFF)}; から始めることはできません。&basic{CHR$(&HFFFF)}; というキーはオブジェクトの終端を表すのに使用されています。

あらかじめ文字列配列内でのインデックスが予測できる場合は [[&basic{OBJGETH};>#OBJGETH]] などを利用し、ヒントとしてインデックスを与えて高速化する方法もあります。




* 構文解析のリファレンス [#ReferenceParsing]

パーサコンビネータを定義したり実際に構文解析を行うための &basic{DEF}; です。


** &basic{PXDEFPC}; …… パーサに名前を付ける [#PXDEFPC]

#basic(noline){{
DEF PXDEFPC PCDEF,PC
}}

*** 引数 [#r0b21aef]
- &basic{PCDEF}; : 新しいパーサコンビネータの &basic{@}; から始まる名前
- &basic{PC}; : パーサコンビネータ ( オブジェクトか &basic{@}; で始まる名前 )

新しいパーサコンビネータを名前を付けて定義します。変数に格納することとの違いは、定義前に利用できるため再帰的な文法が定義できることです。

スキップパーサを指定するには &basic{@_SPACE}; という名前で定義します。スキップパーサはかならず成功する必要があります。


** &basic{PXPARSE}; …… 構文解析を実行する [#PXPARSE]

#basic(noline){{
DEF PXPARSE SOURCE,SPOS,PC OUT RES
}}

*** 引数 [#q213c3d1]
- &basic{SOURCE}; : ソースとなる文字列
- &basic{SPOS}; : 開始位置 ( 通常は 0 )
- &basic{PC}; : パーサコンビネータ ( オブジェクトか &basic{@}; で始まる名前 )

*** 戻り値 [#vd659415]
- &basic{RES}; : 結果オブジェクト
-- &basic{”POS”}; : 終端位置 + 1
-- &basic{”BUFF”}; : 読み取った文字列

構文解析を実行します。結果はオブジェクトで返ってきます。


** &basic{PXGETPCOBJ}; …… パーサの名前からパーサオブジェクトを取得 [#PXGETPCOBJ]

#basic(noline){{
DEF PXPARSE PC OUT PCOBJ
}}

*** 引数 [#ca4a8bac]
- &basic{PC}; : パーサコンビネータ ( オブジェクトか &basic{@}; で始まる名前 )

*** 戻り値 [#s552a595]
- &basic{PCOBJ}; : パーサオブジェクト

[[&basic{PXDEFPC};>#PXDEFPC]] で定義したパーサの名前から、その実体を取得します。未定義の場合はエラーとなります。




* パーサコンビネータのリファレンス [#ParserCombinators]

PX.LIB に用意されているパーサコンビネータです。


** &basic{BASEP}; …… 基底パーサ [#TEXTP]

#basic(noline){{
DEF BASEP()
}}

何も読み取らないパーサを作成します。パースは常に成功します。


** &basic{ACTP}; …… アクションつきパーサ [#ACTP]

#basic(noline){{
DEF ACTP(SUBPC,ACALL)
}}

*** 引数 [#zd2b04df]
- &basic{SUBPC}; : パーサ
- &basic{ACALL}; : &basic{DEF}; の名前

別のあるパーサを引数にとり、その読み取りが成功したときにアクションというコールバック &basic{DEF}; を呼び出すパーサを返します。アクションには引数として結果オブジェクトが渡されます。このオブジェクトは改変することができるので、&basic{”BUFF”}; ( 読み取った文字列 ) を加工したり、追加の情報を設定することもできます。実際に同梱されている計算機のサンプル ( 簡易版のプログラムが [[こちら>#SampleCalc]] にあります ) では &basic{”VALUE”}; という名前で計算結果を格納しています。

&basic{COMMON}; でない &basic{DEF}; を指定する際は &basic{0:}; のように、スロットプレフィックスを加える必要があります。


** &basic{EXTP}; …… 展開パーサ [#EXTP]

#basic(noline){{
DEF EXTP(SUBPC)
}}

*** 引数 [#zd2b04df]
- &basic{SUBPC}; : パーサ

別のあるパーサを引数にとり、そのパーサを展開したものを返します。その展開とは、名前で指定したパーサを取得することです。すでにパーサの実体である場合はそのまま返されます。やっていることは [[&basic{PXGETPCOBJ};>#PXGETPCOBJ]] と完全に同じです。

これが有用なのはパーサを拡張するときです。[[こちら>#ExtendParser]] をご覧ください。


** &basic{TEXTP}; …… 文字列パーサ [#TEXTP]

#basic(noline){{
DEF TEXTP(TEXT)
}}

*** 引数 [#qb96ca0e]
- &basic{TEXT}; : 読み取る文字列

固定の文字列を読み取るパーサを作成します。文字列が &basic{@}; から始まる場合を除いて、&basic{TEXTP}; は省略できます。つまり、パーサコンビネータに文字列を直接指定することができ、自動的に &basic{TEXTP}; に変換されます。


** &basic{CHARP}; …… 文字パーサ [#CHARP]

#basic(noline){{
DEF CHARP([CHARS])
}}

*** 引数 [#a45aa2f4]
- &basic{CHARS}; (省略可) : 想定する文字を並べた文字列。

現在位置の1文字が指定された文字列に含まれる場合、その1文字を読み取るパーサを作成します。空文字列にしたり、引数そのものを省略した場合は、どんな場合でも 1 文字読み取ります ( ただし既に EOF である場合は失敗します ) 。


** &basic{REGEXP}; …… 正規表現パーサ [#REGEXP]

#basic(noline){{
DEF REGEXP(REGEX[,FLAGS])
}}

*** 引数 [#hf9bf7cd]
- &basic{REGEX}; : 正規表現の文字列。
- &basic{FLAGS}; (省略可) : フラグ。省略時は 0

正規表現にマッチする文字列を読み取るパーサを作成します。第1引数には文字列で直接指定します。&basic{REGEX}; で生成した正規表現オブジェクトを指定するわけではありません。またオプションで大文字小文字を区別しないといったフラグを指定できます。

actorbug さんの [[正規表現ライブラリ>3gou:Toukou/正規表現ライブラリ]] を移植して使用しています。

*** 利用できる正規表現の一覧 [#MetaCharacters]

[[こちら>3gou:Toukou/正規表現ライブラリ]] よりそのまま引用しています。

|~メタ文字|~説明|
|\|直後のメタ文字を無効化して通常文字として扱う|
|\n|LF、CHR$(10)|
|\r|CR、CHR$(13)|
|\t|Tab、CHR$(9)|
|\xFF|16進2桁で文字コード指定|
|\uFFFF|16進4桁で文字コード指定|
|[]|文字クラス、括弧内の文字のいずれかにマッチ、-で範囲指定可能|
|[^]|否定文字クラス、括弧内の文字以外にマッチ、-で範囲指定可能|
|.|任意の1文字(改行を除く)にマッチ、単一行モードなら改行もマッチ|
|\d|数字、[0-9]と同じ|
|\D|非数字、[^0-9]と同じ|
|\w|単語構成文字、[a-zA-Z0-9_]と同じ|
|\W|非単語文字、[^a-zA-Z0-9_]と同じ|
|\s|空白文字、[ \n\r\t]と同じ|
|\S|非空白文字、[^ \n\r\t]と同じ|
|^|文字列の先頭、複数行モードなら改行の次にもマッチ|
|$|文字列の末尾 or 最後が改行ならその前、複数行モードなら改行の前にもマッチ|
|\A|文字列の先頭|
|\Z|文字列の末尾 or 最後が改行ならその前|
|\z|文字列の末尾|
|\b|単語境界|
|\B|単語境界以外|
|\1〜\9|後方参照|
|()|グループ化 & キャプチャ|
|(?:)|グループ化(キャプチャなし)|
|(?=)|肯定の先読み|
|(?!)|否定の先読み|
|(?<=)|肯定の戻り読み(通常文字・文字クラス・一部アンカーのみ使用可)|
|(?<!)|否定の戻り読み(通常文字・文字クラス・一部アンカーのみ使用可)|
|(?>)|アトミックなグループ|
|(?修飾子)|モード修飾子(?i)(?-i)など|
|(?修飾子:)|モード修飾子の範囲(?i:...)など|
|&#x7c;|選択|
|*|直前の式の0回以上の繰り返し、{0,}と同じ|
|+|直前の式の1回以上の繰り返し、{1,}と同じ|
|?|直前の式が0回か1回現れる、{0,1}と同じ|
|{n}|直前の式のn回の繰り返し|
|{m,}|直前の式のm回以上の繰り返し|
|{m,n}|直前の式のm回以上n回以下の繰り返し|
|*?|直前の式の0回以上の繰り返し(最短マッチ)|
|+?|直前の式の1回以上の繰り返し(最短マッチ)|
|??|直前の式が0回か1回現れる(最短マッチ)|
|{m,}?|直前の式のm回以上の繰り返し(最短マッチ)|
|{m,n}?|直前の式のm回以上n回以下の繰り返し(最短マッチ)|
|*+|直前の式の0回以上の繰り返し(強欲)|
|++|直前の式の1回以上の繰り返し(強欲)|
|?+|直前の式が0回か1回現れる(強欲)|
|{m,}+|直前の式のm回以上の繰り返し(強欲)|
|{m,n}+|直前の式のm回以上n回以下の繰り返し(強欲)|

*** フラグ一覧 [#RegexFlags]

[[こちら>3gou:Toukou/正規表現ライブラリ]] よりそのまま引用しています。

オプションは数値で指定してください。複数指定する場合は足します。

|~オプション|~値|~説明|
|RXOPT_IC|1|大文字小文字を区別しない|
|RXOPT_SL|2|単一行モード("."が改行にもマッチする)|
|RXOPT_ML|4|複数行モード("^","$"が改行前後にもマッチする)|

** &basic{OPTP}; …… 省略可能パーサ [#OPTP]

#basic(noline){{
DEF OPTP(SUBPC)
}}

*** 引数 [#fe44584f]
- &basic{SUBPC}; : パーサ

別のあるパーサを引数にとり、読み取りに失敗しても位置を戻して成功と扱うパーサを作成します。

失敗することがないので省略可能な部分を定義するのに利用します。


** &basic{AHEADP}; …… 肯定先読みパーサ [#AHEADP]

#basic(noline){{
DEF AHEADP(SUBPC)
}}

*** 引数 [#s72990c7]
- &basic{SUBPC}; : パーサ

別のあるパーサを引数にとり、読み取ったあとに位置を戻すパーサを作成します。読み取れなかった場合は失敗します。


** &basic{NOTP}; …… 否定先読みパーサ [#NOTP]

#basic(noline){{
DEF NOTP(SUBPC)
}}

*** 引数 [#z052cad2]
- &basic{SUBPC}; : パーサ

別のあるパーサを引数にとり、読み取れた場合は逆に失敗し、読み取れなかった場合にその位置で成功するパーサを作成します。

特定の識別子を読み取り、直後にアルファベットや数字などの、識別子を構成する文字が続いていないかどうかを確認できます。


** &basic{ORP}; …… 選択パーサ [#ORP]

#basic(noline){{
DEF ORP(...)
}}

*** 引数 [#r6c3279c]
- &basic{...}; : 1個以上のパーサ

別の複数のパーサを引数にとり、それらを順番にパースしていき、どれかが成功すればその結果をこのパーサの結果とするパーサを返します。

最初に成功したパーサの結果オブジェクトが、このパーサの結果オブジェクトとなります。どれかが成功した後は、それ以降のパーサは実行されません。


** &basic{SEQP}; …… シーケンスパーサ [#SEQP]

#basic(noline){{
DEF SEQP(...)
}}

*** 引数 [#m1e7c1ed]
- &basic{...}; : 1個以上のパーサ

*** 追加の結果オブジェクトのキー [#t25254e1]
- &basic{”SEQ#”}; : 結果の数。&basic{SEQP}; に渡した引数の数と一致します。
- &basic{”SEQ0”}; : シーケンスの 0 番目の (最初の) パーサの結果。同様に 1 番目以降も指定できます。&basic{”SEQ0.BUFF”}; のようにすれば、シーケンスの最初のパーサが読み取った文字列を取得できます。

別の複数のパーサを引数にとり、それらを順番にパースしていき、最後まで成功した場合をこのパーサが成功したと扱うパーサを返します。

それぞれのパーサの結果オブジェクトが、このパーサの結果オブジェクトに格納されます。それらすべてを後から参照することが可能となります。


** &basic{REPP}; …… 繰り返しパーサ [#REPP]

#basic(noline){{
DEF REPP(SUBPC,MINC,MAXC)
}}

別のあるパーサを引数にとり、指定した回数の範囲、繰り返されるパーサを返します。ただし、正規表現が使える場合は、[[&basic{REGEXP};>#REGEXP]] の方が高速です。

*** 引数 [#zbacd094]
- &basic{SUBPC}; : パーサ
- &basic{MINC}; : 最小回数。制限を設けない場合は 0 を指定。
- &basic{MAXC}; : 最大回数。制限を設けない場合は 0 を指定。

*** 追加の結果オブジェクトのキー [#d5bdd86e]
- &basic{”REP#”}; : 結果の数。繰り返し読み取った回数です。
- &basic{”REP0”}; : 最初に読み取った結果。同様に 1 番目以降も指定できます。&basic{”REP0.BUFF”}; のようにすれば、1 回目にパーサが読み取った文字列を取得できます。

それぞれの回の結果オブジェクトが、このパーサの結果オブジェクトに格納されます。それらすべてを後から参照することが可能となります。


** &basic{LISTP}; …… リストパーサ [#LISTP]

#basic(noline){{
DEF LISTP(SUBPC,DELPC)
}}

*** 引数 [#y03a349f]
- &basic{SUBPC}; : 項目を読み取るパーサ
- &basic{DELPC}; : 区切りを読み取るパーサ

*** 追加の結果オブジェクトのキー [#p4140182]
- &basic{”LIST#”}; : 項目の数。項目は (項目数 - 1) 番目、デリミタは (項目数 - 2) 番目まで有効です。
- &basic{”LISTSUB0”}; : 0 番目の項目の結果オブジェクト。1 番目以降も指定できます。
- &basic{”LISTDEL0”}; : 0 番目の項目の結果オブジェクト。1 番目以降も指定できます。

項目用のパーサとデリミタ ( 区切り ) 用のパーサを引数にとり、デリミタで区切られたリストを読み取るパーサを返します。

それぞれの項目の結果オブジェクトが、このパーサの結果オブジェクトに格納されます。




* オブジェクトのリファレンス [#ReferenceObject]

階層化可能なオブジェクトを実現するための &basic{DEF}; 。JSON を扱うこともできます。


** &basic{OBJECT}; …… 空のオブジェクトを生成 [#OBJECT]

#basic(noline){{
DEF OBJECT OUT OBJ
}}

*** 戻り値 [#x6bc8960]
- &basic{OBJ}; : 空のオブジェクト

新しい空のオブジェクトを生成します。やっていることは &basic{ARRAY$(0)}; と同じです。


** &basic{OBJGET}; …… オブジェクトから値を取得 [#OBJGET]

#basic(noline){{
DEF OBJGET OBJ,PATH OUT VALUE
}}

*** 引数 [#a1114ed9]
- &basic{OBJ}; : オブジェクト
- &basic{PATH}; : 取得するためのキー。ピリオドで階層を指定できます。

*** 戻り値 [#eb51724c]
- &basic{VALUE}; : 取得した値

オブジェクトから、キーを指定して値を取得します。

値は数値や文字列のようにプリミティブな型である場合も、オブジェクトである場合もあります。取得したオブジェクトからさらに値を取得したい場合、[[&basic{OBJGET};>#OBJGET]] で取得したオブジェクトに対してさらに [[&basic{OBJGET};>#OBJGET]] を使うより、ピリオドに続けて一気に指定した方が高速です。

#basic(noline){{
' 上よりも下のほうが速い
OBJGET(OBJGET(OBJ,"FOO"),"BAR")
OBJGET(OBJ,"FOO.BAR")
}}

なおキーが存在しない場合はデフォルト値が返ってきます。&basic{TYPEOF}; で判定することができますが、存在を確認するだけならば [[&basic{OBJHAS};>#OBJHAS]] という関数もあります。


** &basic{OBJSET}; …… オブジェクトに値を設定 [#OBJSET]

#basic(noline){{
DEF OBJSET OBJ,PATH,VALUE
}}

*** 引数 [#p82585b0]
- &basic{OBJ}; : オブジェクト
- &basic{PATH}; : 設定するためのキー。ピリオドで階層を指定できます。
- &basic{VALUE}; : 設定する値

オブジェクトに、キーを指定して値を設定します。[[&basic{OBJGET};>#OBJGET]] と同じくピリオドを使って階層を指定できます。キーが存在しなければ追加され、存在すれば上書きされます。


** &basic{OBJHAS}; …… オブジェクトにキーが存在するか確認 [#OBJHAS]

#basic(noline){{
DEF OBJHAS OBJ,PATH OUT HAS
}}

*** 引数 [#q71abf9a]
- &basic{OBJ}; : オブジェクト
- &basic{PATH}; : 存在を確認するキー。ピリオドで階層を指定できます。

*** 戻り値 [#yf575346]
- &basic{HAS}; : キーが存在するかどうか

オブジェクトに、指定したキーがあるかどうかを確認します。存在すれば &basic{#TRUE}; しなければ &basic{#FALSE}; が返ります。

オブジェクトにあるキーの一覧を取得するには [[&basic{OBJPATHS};>#OBJPATHS]] を使います。


** &basic{OBJDEL}; …… オブジェクトからキーを削除 [#OBJDEL]

#basic(noline){{
DEF OBJDEL OBJ,PATH
}}

*** 引数 [#e4f33325]
- &basic{OBJ}; : オブジェクト
- &basic{PATH}; : 削除するキー。ピリオドで階層を指定できます。

オブジェクトから、キーを削除します。そのキーの値がオブジェクトだった場合はオブジェクトごと削除されます。キーが存在しない場合は何も起こりません。


** &basic{OBJPATHS}; …… オブジェクトからキーを削除 [#OBJPATHS]

#basic(noline){{
DEF OBJPATHS OBJ,SUB OUT PATHS
}}

*** 引数 [#e4f33325]
- &basic{OBJ}; : オブジェクト
- &basic{SUB}; : 子オブジェクト以下のキーも取得するかどうか

*** 戻り値 [#u92c6e81]
- &basic{PATHS}; : キーの一覧

オブジェクトのキーの一覧を文字列配列で取得します。第2引数に &basic{#FALSE}; を指定したときは直下のキーのみを取得しますが、&basic{#TRUE}; を指定したときはキーの値がオブジェクトだった場合に再帰的にそのオブジェクトのキーの一覧も加えます。このキーの一覧はソートされています。


** &basic{OBJGETH}; …… オブジェクトからインデックスヒントつきで値を取得 [#OBJGETH]

#basic(noline){{
DEF OBJGETH OBJ,INDEX,PATH OUT VALUE
}}

*** 引数 [#h06f9d42]
- &basic{OBJ}; : オブジェクト
- &basic{INDEX}; : キーがある可能性が高いインデックス。
- &basic{PATH}; : 取得するためのキー。ピリオドで階層を指定できます。

*** 戻り値 [#w25e8779]
- &basic{VALUE}; : 取得した値

オブジェクトから、キーを指定して値を取得します。

[[&basic{OBJGET};>#OBJGET]] と違うのは、インデックスという追加の引数を指定するという点です。インデックスとはオブジェクトの実体である文字列配列における添え字のことです。オブジェクトに入るキーが初めから決まっている、あるいはそうでなくてもキーのあるインデックスが固定値または計算で予測できる場合はヒントとしてそのインデックスを渡すことで、インデックスで指定した位置がまさにそのキーであった場合にはキーを検索せずそのままその位置を参照するため、高速になります。インデックスが異なった場合、つまり指定したインデックスにキーがなかった場合は &basic{OBJGET}; と同様にキーを検索します。

&basic{OBJGETH}; では数値と文字列のみを取得できます。子オブジェクトには対応していません。


** &basic{OBJSETH}; …… オブジェクトにインデックスヒントつきで値を設定 [#OBJSETH]

#basic(noline){{
DEF OBJSETH OBJ,INDEX,PATH,VALUE
}}

*** 引数 [#l0fddbf1]
- &basic{OBJ}; : オブジェクト
- &basic{INDEX}; : キーがある可能性が高いインデックス。
- &basic{PATH}; : 設定するためのキー。ピリオドで階層を指定できます。
- &basic{VALUE}; : 設定する値

オブジェクトに、キーを指定して値を設定します。[[&basic{OBJGET};>#OBJGET]] と同じくピリオドを使って階層を指定できます。キーが存在しなければ追加され、存在すれば上書きされます。

[[&basic{OBJSET};>#OBJSET]] と違う点は [[&basic{OBJGETH};>#OBJGETH]] にある説明と同じく、キーのある可能性が高い位置を同時に指定して、その位置が指定したキーだった場合にその位置を直接参照することです。

&basic{OBJSETH}; では数値と文字列のみを設定できます。子オブジェクトには対応していません。


** &basic{ISOBJECT}; …… オブジェクトかどうかを判定 [#ISOBJECT]

#basic(noline){{
DEF ISOBJECT VALUE OUT ISOBJECT
}}

*** 引数 [#a2fa1624]
- &basic{VALUE}; : 判定する値

*** 戻り値 [#m7209519]
- &basic{ISOBJECT}; : オブジェクトかどうか

渡された値がオブジェクトかどうかを調べます。やっていることは &basic{TYPEOF(VALUE)==#T_STRARRAY}; と同じです。


** &basic{OBJFIND}; …… オブジェクトのキーを検索 (internal) [#OBJFIND]

#basic(noline){{
DEF OBJFIND OBJ,PATH,SINDEX,EINDEX OUT INDEX
}}

*** 引数 [#r7f6c2bd]
- &basic{OBJ}; : オブジェクト
- &basic{PATH}; : 検索するキー。ピリオドで階層を指定できます。
- &basic{SINDEX}; : 検索する先頭位置 - 1 。オブジェクトの先頭から検索する場合は -1 です。
- &basic{EINDEX}; : 検索する末尾位置 + 1 。オブジェクトの末尾まで検索する場合は &basic{LEN(OBJ)}; です。

*** 戻り値 [#t1b72bca]
- &basic{INDEX}; : キーのあるオブジェクト上のインデックス

オブジェクトのキーのあるべき位置を取得します。キーがない場合はキーを追加 ( &basic{INSERT}; ) する場合の位置となります。キーの値がオブジェクトだった場合は先頭の位置を返します ( そのオブジェクトの先頭のキーはその次の位置にあります )。終端位置は同じキーで [[&basic{OBJFINDEND};>#OBJFINDEND]] を使用します。

通常使用ではあまり推奨していません。


** &basic{OBJFINDEND}; …… オブジェクトのキーを検索 (internal) [#OBJFINDEND]

#basic(noline){{
DEF OBJFINDEND OBJ,PATH,SINDEX,EINDEX OUT INDEX
}}

*** 引数 [#jc9021d2]
- &basic{OBJ}; : オブジェクト
- &basic{PATH}; : 検索するキー。ピリオドで階層を指定できます。
- &basic{SINDEX}; : 検索する先頭位置 - 1 。通常は [[&basic{OBJFIND};>#OBJFIND]] の戻り値を使用します。
- &basic{EINDEX}; : 検索する末尾位置 + 1 。オブジェクトの末尾まで検索する場合は &basic{LEN(OBJ)}; です。

*** 戻り値 [#yc546d28]
- &basic{INDEX}; : キーのあるオブジェクト上のインデックス

キーの値がオブジェクトの場合、終端位置を取得するのに使われます。&basic{OBJFIND(OBJ,PATH+”.”+CHR$(&HFFFF),SINDEX,EINDEX)}; と同じです。

通常使用ではあまり推奨していません。


** &basic{JSON2OBJ}; …… JSON から オブジェクトを生成 [#JSON2OBJ]

#basic(noline){{
DEF JSON2OBJ JSON OUT VALUE
}}

*** 引数 [#v7a08774]
- &basic{JSON}; : JSON 文字列

*** 戻り値 [#gb9b0ce7]
- &basic{VALUE}; : 生成されたオブジェクトまたはプリミティブ値

[[JSON>https://ja.wikipedia.org/wiki/JSON]] をもとに新しいオブジェクトを生成します。SmileBASIC の文字列リテラルに直接 JSON を記述することも踏まえて文字列リテラルを &basic{”}; ではなく &basic{'}; で括ることもできるように拡張されています。エスケープシーケンスも有効です。


** &basic{OBJ2JSON}; …… オブジェクトから JSON を生成 [#OBJ2JSON]

#basic(noline){{
DEF OBJ2JSON VALUE OUT JSON
}}

*** 引数 [#h198461c]
- &basic{VALUE}; : オブジェクトまたはプリミティブ値

*** 戻り値 [#ta00bc37]
- &basic{JSON}; : 生成された JSON 文字列

オブジェクトまたは単独の数値、文字列から [[JSON>https://ja.wikipedia.org/wiki/JSON]] を生成します。生成される JSON にはスペースやインデントは含まれません。




* サンプル [#Samples]

** 計算機 [#SampleCalc]

テキトーに作ったものです

#basic{{
OPTION STRICT
ACLS

LOAD "PXIILIB-0.3.0/PXII.LIB",1
EXEC 1

INITPC
LOOP
 LINPUT "?";EXPR
 PRINT "=";EVAL(EXPR)
ENDLOOP

DEF INITPC
 PXDEFPC @_SPACE,REGEXP("[\x20]*")
 PXDEFPC @EXPR,ACTP(LISTP(@TERM,CHARP("+-")),"0:ONCALCB")
 PXDEFPC @TERM,ACTP(LISTP(@FACT,CHARP("*/%")),"0:ONCALCB")
 PXDEFPC @FACT,ACTP(SEQP(REGEXP("[-]*"),ORP(@REAL,SEQP("(",@EXPR,")"))),"0:ONFACT")
 PXDEFPC @REAL,REGEXP("([0-9]+(\.[0-9]*)?|[0-9]*\.[0-9]+)")
END

DEF EVAL(EXPR)
 DIM RES=PXPARSE(EXPR,0,@EXPR)
 IF OBJGET(RES,"SUCCESS") THEN
  RETURN OBJGET(RES,"VALUE")
 ELSE
  RETURN POW(2,1024)*0 'nan
 ENDIF
END

DEF ONFACT RES
 DIM SIGN=1-(LEN(OBJGET(RES,"SEQ0.BUFF")) MOD 2)*2
 DIM BUFF=OBJGET(RES,"SEQ1.BUFF")
 IF LEFT$(BUFF,1)=="(" THEN
  OBJSET RES,"VALUE",SIGN*OBJGET(RES,"SEQ1.SEQ1.VALUE")
 ELSE
  OBJSET RES,"VALUE",SIGN*VAL(BUFF)
 ENDIF
END

DEF ONCALCB RES
 DIM I,O,A,B
 A=OBJGET(RES,"LISTSUB0.VALUE")
 FOR I=1 TO OBJGET(RES,"LIST#")-1
  O=OBJGET(RES,"LISTDEL"+STR$(I-1)+".BUFF")
  B=OBJGET(RES,"LISTSUB"+STR$(I)+".VALUE")
  A=CALCB(O,A,B)
 NEXT
 OBJSET RES,"VALUE",A
END

DEF CALCB(O,A,B)
 CASE O
  WHEN "+":RETURN A+B
  WHEN "-":RETURN A-B
  WHEN "*":RETURN A*B
  WHEN "/":RETURN A/B
  WHEN "%":RETURN A MOD B
 ENDCASE
END
}}

// * スクリーンショット・動画 [#Screenshots]
// 動作しているところを撮影した写真や動画があれば貼り付けてください。

// ページを作成すると、画像の添付ができます。(作成する前はできません)
// 作成後のページの右上の「Wikiの機能」→「画像を添付する」からアップロードできます。
// (添付ファイルはページごとに別個に管理されています。必ず作成後のページに添付してください)
// 画像をアップロードしたら、以下の書式でページ内に貼り付けられます。
// #ref(添付ファイル名)

// 以下の書式で、YouTube やニコニコ動画を貼り付けられます。
// #youtube(0HWX7kovgY8)
// #nicovideo(sm13684820)

// Twitter の投稿は以下のように書けば埋め込めます。
// #tweet(https://twitter.com/hosiken/status/615920904929849344)




* 公開キー [#PublicKey]

プチコン4用の公開キーです。

#ptcmid(4RK33W34V)




* 更新履歴 [#Releases]


** v0.3.1 ( 2020/03/26 ) [#Release_0_3_1]

- パーサを展開するための &basic{EXTP}; を追加。パーサを再定義する際に使用できます。詳しい解説は [[パーサを拡張する>#ExtendParsers]] を参照。


** v0.3.0 ( 2020/03/23 ) [#Release_0_3_0]

- 初版。




* コメント [#Comments]

PXLIB に関する感想や意見・要望ならなんでもどうぞ。バグ報告もこちらでお願いします。
PXIILIB に関する感想や意見・要望ならなんでもどうぞ。バグ報告もこちらでお願いします。

#comment

表示モード : [ スマホ・3DS対応表示 | クラシック表示 ]
PukiWiki 1.4.7 Copyright © 2001-2006 PukiWiki Developers Team. License is GPL. Based on "PukiWiki" 1.3 by yu-ji
ページの処理時間 : 0.258 秒 | ログイン
Copyright(C) 2011-2019 プチコンまとめWiki