#author("2026-08-31T22:19:11+09:00","default:guide","guide") #author("2026-09-04T00:06:05+09:00","default:mcp","bot-mcp") * Pkript/文法 [#m311b4d4] Pkript の言語仕様です。 Pkript の言語仕様リファレンス。詳細は各ページへ。 ** 個別ページ [#h03095c8] - [[Pkript/文法/変数]]: 変数宣言、スコープ、再代入 - [[Pkript/文法/関数]]: 関数定義、アロー関数、エントリポイント - [[Pkript/文法/コンテキスト]]: e オブジェクト、e.user、リクエスト引数 #contents ** ページ一覧 [#pages] - [[Pkript/文法/変数]]: 変数宣言、スコープ、グローバル変数、再代入 - [[Pkript/文法/データ型]]: プリミティブ型、オブジェクト、数値リテラル - [[Pkript/文法/演算子]]: 単項演算子と二項演算子、優先順位、??、?.、スプレッド - [[Pkript/文法/関数]]: 関数定義、アロー関数、エントリポイント、可変長引数 - [[Pkript/文法/コンテキスト]]: e オブジェクト、e.user、リクエスト引数 - [[Pkript/文法/制御構文]]: if、switch、for、while、ラベル - [[Pkript/文法/テンプレートリテラル]]: 埋め込み式、バッククォート構文 - [[Pkript/文法/JSX]] : HTMLタグ式、自動エスケープ仕様 - [[Pkript/文法/正規表現]]: リテラル構文、フラグ、バックトラック制限 - [[Pkript/文法/制御構文]]: if、for、while、switch、ラベル - [[Pkript/文法/エラー処理]]: try / catch、上限エラーの仕様 - [[Pkript/文法/JSX]]: HTMLタグ式、自動エスケープ、サニタイザとの関係 - [[Pkript/文法/正規表現]]: リテラル構文、フラグ、使えるメソッド、バックトラック制限 - [[Pkript/文法/エラー処理]]: try / catch、catch できない上限エラー - [[Pkript/文法/import]]: モジュール読み込み、信頼度制限 - [[Pkript/文法/フォーム]]: 出力できるフォーム要素、送信値の受け取り ** 見出し [#d18cd807] #contents ** 概要 [#summary] ** 変数 [#g61e0400] JavaScript風の構文で書き、PHPには触れない。文の書き方はほぼJavaScriptと同じで、改行があれば文末のセミコロンを省略できる。 var, let, const が使えます。 #highlight{{ const KEYWORDS = ["function", "return"]; var a = 1; let b = 2; const c = 3; function plugin_hello_convert(e) { const name = e.args[0] || "World"; return "<p>Hello, " + htmlsc(name) + "!</p>"; } }} - var, let: 再代入可能 - const: 再代入不可 - 未宣言代入: `let` や `const` を付けずに `$args = ...` のように代入した場合、現在のローカルスコープで自動定義されます。 |~項目|~書けるもの|~詳細|h |宣言|var / let / const|[[Pkript/文法/変数]]| |型|String, Number, Boolean, Null, Array, Object, RegExp|[[Pkript/文法/データ型]]| |関数|function 宣言、アロー関数、クロージャ|[[Pkript/文法/関数]]| |制御|if / else、switch、while、do..while、for、for..of、for..in、ラベル|[[Pkript/文法/制御構文]]| |文字列|"..." / '...' / `...${式}...`|[[Pkript/文法/テンプレートリテラル]]| |HTML出力|文字列連結、JSX記法|[[Pkript/文法/JSX]]| |例外|try / catch(throw と finally は使えない)|[[Pkript/文法/エラー処理]]| |分割|import "スクリプト名";|[[Pkript/文法/import]]| *** グローバル変数 [#wc579df0] 関数の外側にも変数を書けます。エントリポイントが呼ばれる前に、書いた順で評価されます。 エントリポイントは plugin_◯◯_convert / inline / action の3種類で、第1引数にコンテキスト e を受け取る。 const KEYWORDS = ["function", "return", "const"]; let count = 0; function plugin_hello_convert(e) { count = count + 1; return KEYWORDS.includes(e.args[0]) ? "keyword" : "no"; } ** 関連 [#related] - 前に書いた変数は使えますが、後ろに書いた変数は使えません。 - 初期値の中から関数を呼べます。関数は順序に関係なく見えています。 - 関数と変数は同じ名前空間です。同じ名前を付けるとエラーになります。 - let と var の書き換えが効くのは、その1回の呼び出しの中だけです。#pkript の呼び出しごとに初期値へ戻ります。 *** セミコロン省略 [#md666494] 改行が入っていれば、文末のセミコロン ; は省略できます。 var a = 1 var b = 2 return a + b ** 関数 [#q479ccbc] *** 関数宣言 [#w8eba433] トップレベルに関数を定義します。 function plugin_hello_convert(e) { return "Hello World!"; } - plugin_◯◯_convert / inline / action がエントリポイントになります。 - それ以外の関数はスクリプト内の共通処理として呼び出せます。 *** アロー関数 [#m0e28e28] アロー関数式が使えます。 const double = (x) => x * 2; const greet = () => "Hello"; エントリポイントをアロー関数で書くこともできます。 const plugin_hello_convert = (e) => { return "Hello, " + e.args[0] + "!"; }; 定義された場所のスコープをキャプチャします(クロージャ)。 function makeAdder(n) { return (x) => x + n; } ** 引数の受け取り方 [#n5575aea] *** 1. func_get_args [#r7308350] PHPプラグインと同じ感覚で引数を取得できます。 - func_get_args(): 引数配列を取得 - func_num_args(): 引数の個数を取得 - func_get_arg(n): n番目の引数を取得 function plugin_hello_convert() { var args = func_get_args(); return "引数: " + args[0]; } *** 2. e オブジェクト [#y9343872] エントリポイントの第1引数にコンテキストオブジェクトが渡されます。 |~プロパティ|~型|~内容|h |e.args|Array|渡された引数の配列| |e.opts|Object|key=value 形式のオプション| |e.body|String|複数行記法の本文| |e.page|String|呼び出し元ページ名| |e.name|String|スクリプト名| |e.type|String|"convert" / "inline" / "action"| |e.vars|Object|POST/GET のフォーム値| |e.user|Object|閲覧しているユーザー| |e.method|String|"GET" または "POST"| - e.opts: #pkript(list, foo, bar, class=fruit) と渡すと e.opts.class で "fruit" が取れます。 - e.body: #pkript(list){{ 本文 }} の複数行テキストが入ります。 - e.vars: フォームの送信値が入ります。パスワード等の機密キーは自動で除外されます。 **** e.user [#e_user] いま見ている人が誰かを表すオブジェクトです。 |~プロパティ|~型|~内容|h |e.user.name|String|ログイン名。未ログインは空文字列| |e.user.fullname|String|表示名。未ログインは空文字列| |e.user.groups|Array|所属グループ。未ログインは空配列| if (e.user.name == "") return "<p>ログインしてください</p>"; return "<p>" + htmlsc(e.user.fullname) + " さん</p>"; - 3つのプロパティは''常に存在します''。この言語にはオプショナルチェーンが無く、存在しないプロパティを読むとエラーになるため、未ログインは null ではなく空文字列と空配列で表します。 - groups には $auth_groups で解決したグループのほか、''ユーザー名自身と valid-user'' が入ります。したがってログイン中は空配列になりません。 - fullname が name と違う値になるのは、AUTH_TYPE_EXTERNAL / AUTH_TYPE_SAML で $ldap_user_account を有効にし、LDAPから表示名を引いている場合です。BASIC認証やフォーム認証では name と同じ値になります。 - ''管理者かどうかは取得できません。'' PukiWiki には管理者という身元が存在せず、pkwk_login() はそのリクエストで送られたパスワードを $adminpass と比較するだけで結果をどこにも残さないためです。権限で分岐するなら wiki.canWrite(page) か e.user.groups を使ってください。 - 閲覧者ごとに出力が変わるページになります。リバースプロキシやブラウザキャッシュを挟む構成では、ある人向けのHTMLが別の人に返る可能性があります。 ** データ型 [#uf2c8065] |~型|~例|h |String|"文字列", '文字列'| |Number|10, 3.14| |Boolean|true, false| |Array|[1, 2, 3]| |Object|{ a: 1, b: "text" }| |Null|null| |RegExp|/[a-z]+/i| *** 数値リテラル [#num_literal] 255 0xff 0XFF // 10進 / 16進 0b1010 0o17 // 2進 / 8進 1e3 2e-3 1.5e2 // 指数 1_000_000 0x1_F // 桁区切り - _ は''数字と数字の間にだけ''書けます。1_ や _1、1__0 は書けません。 - . か指数の付いた数は、1.0 や 1e3 のように整数に着地しても Number(小数)のままです。 - 整数として表せない大きさになると小数になります。JavaScript が正確でなくなるのと同じ位置です。 - 数値の直後に識別子の文字が続くとエラーになります(123abc、0x)。 ** テンプレートリテラル [#la9e7a31] バッククォートで囲むと、${} の中に式を書けます。改行もそのまま入ります。 const cls = e.opts["class"]; const rows = e.args.map((a) => `<li>${htmlsc(a)}</li>`).join(""); return `<ul class="${cls}"> ${rows} </ul>`; - ${} の中には式なら何でも書けます。関数呼び出しも、テンプレートの入れ子も通ります。 - 値は String() と同じ規則で文字列になります。null は空文字列です。 - エスケープは文字列と同じものが使えるほか、\` と \$ が増えます。 - 文字列の中の } は ${} を閉じません。 ** JSX記法 [#hc6d35f8] HTMLを式としてそのまま書けます。文字列連結の代わりに使います。 function plugin_hello_convert(e) { const name = e.args[0]; return <p class="greeting">こんにちは、{name} さん</p>; } - {} の中の文字列は自動でエスケープされます。htmlsc() を重ねると二重エスケープになります。 - JSX要素どうしの埋め込みはエスケープされません。入れ子がそのまま組み立てられます。 - PKRIPT_JSX を 0 にすると無効になり、< は比較演算子としてだけ働きます。 書ける形、エスケープの規則、サニタイザとの関係は [[Pkript/文法/JSX]] にまとめています。 ** 正規表現 [#e1751689] /パターン/フラグ と書きます。値としての型は RegExp で、変数にも入れられます。 const DATE = /(\d{4})-(\d{2})-(\d{2})/; if (DATE.test(line)) { return line.replace(DATE, "$1年$2月$3日"); } - フラグは g(全件)、i(大小無視)、m(複数行)、s(. が改行にも一致)が使えます。 - UTF-8としての解釈は常に有効です。 - PKRIPT_REGEX を 0 にすると無効になり、/ は除算としてだけ働きます。 書ける形、使えるメソッド、暴走を止める仕組みは [[Pkript/文法/正規表現]] にまとめています。 ** 制御構文 [#w0248293] *** 条件分岐 [#ja8ebed6] if (cond) { // ... } else if (other) { // ... } else { // ... } *** ループ [#iec1e363] while (cond) { // ... } do { // ... } while (cond); for (let i = 0; i < n; i++) { // ... } for (const item of array) { // ... } for (const key in object) { // ... } - break と continue が使えます。 - for..of は Array と String を走査します。要素の''値''が入ります。 - for..in はキーが入ります。Array に使うと添字が''文字列''で入るので、配列は for..of を使ってください。 - ループ変数は繰り返しごとに新しく束縛されるため、const が使えます。 - do..while は条件より先に本体を実行するので、必ず1回は回ります。 *** ラベル [#label] ループに名前を付けると、break と continue でどのループを指すか書けます。 outer: for (const row of rows) { for (const cell of row) { if (cell == "") continue outer; // 次の row へ if (cell == "!") break outer; // 二重ループごと抜ける } } - ラベルは break / continue の''同じ行に''書きます。改行は文の終わりなので、次の行の識別子はラベルではなく別の文になります。 - continue のラベルはループに付いている必要があります。ブロックに付けたラベルへは break だけが届きます。 - ラベル付きの break は switch を素通りします。switch が飲み込むのはラベルの無い break だけです。 - 存在しないラベルと二重定義のラベルは''解析時''にエラーになります。 switch (e.type) { case "convert": return "ブロック"; case "inline": return "インライン"; default: return "その他"; } - JavaScript と同じくフォールスルーします。break を書かないと次の case へ流れます。 - case の比較は === 相当です。"1" と 1 は一致しません。 - default はどこに置いても構いません。一致する case が無ければ default から末尾まで走ります。 - switch の中の break は switch だけを抜けます。continue は外側のループのものとして働きます。 ---- ** エラー処理 (try / catch) [#p5b52e22] try { const data = JSON.parse(wiki.source("Settings")); } catch (err) { return "<p>" + htmlsc(err.message) + "</p>"; } - catch (err) の変数は省略できます(catch { ... })。 - err は message を持つオブジェクトです。エラー文言と発生位置が入ります。 - return, break, continue は try を素通りします。 - finally と throw は使えません。 *** リソース上限は catch できません [#daf8da79] 実行時間、ステップ数、メモリ、ループ回数、再帰の深さ、文字列の長さ、配列の要素数、ページの参照・書き込み回数、wiki.convert の回数。これらが上限に達したときのエラーは catch を素通りして実行を終わらせます。 暴走したループを try で包んで走り続けられると、上限そのものが意味を失うためです。 ---- ** モジュール読み込み (import) [#g4206575] 他のスクリプトで定義された関数を読み込みます。 import "util"; function plugin_hello_convert(e) { return util_wrap("p", e.args[0]); } - import はトップレベルでのみ使えます。 - 循環参照や重複読み込みは自動で1回に抑えられます。 - 読み込んだ側の関数名やグローバル変数が衝突するとエラーになります。後から上書きはしません。 - 読み込まれたスクリプトの plugin_◯◯_convert などは呼ばれません。 - 読み込む本数と入れ子の深さに上限があります(既定16本、深さ4)。 - 自分より信頼度の低いスクリプトは読み込めません。詳しくは [[Pkript/設定]] を参照してください。 ** フォーム [#u9c630d5] フォーム要素を安全に出力できます。 form, input, textarea, select, option, optgroup, label, button, fieldset, legend function plugin_form_convert(e) { return "<form method=\"post\">" + "<input type=\"hidden\" name=\"plugin\" value=\"pkript\">" + "<input type=\"hidden\" name=\"script\" value=\"form\">" + "<input type=\"text\" name=\"name\">" + "<input type=\"submit\" value=\"送信\">" + "</form>"; } function plugin_form_action(e) { if (e.method != "POST") return "<p>POSTで送信してください</p>"; return "<p>こんにちは、" + htmlsc(e.vars["name"]) + " さん</p>"; } - form action は自Wikiへの相対パスに限定されます。 - password, file, image タイプの input は禁止されています。 - [[Pkript/API]] - 組み込み関数とオブジェクト - [[Pkript/設定]] - 文法機能の有効/無効、上限値 - [[Pkript/サンプル]] - 実用スクリプト集 - [[Pkript/エラー一覧]] - エラーメッセージと対処法