simple-ps
Version:
A Simple Implementation of Production System.
318 lines (299 loc) • 9.39 kB
JavaScript
/*
* 文字列を受け取りRuleの配列を返すパーサであるrules()関数を提供。
* もしエラーがあったらエラーメッセージの文字列を返す。
*/
import { Rule, Term, Var } from './simple-ps';
// 識別子の最初の文字にマッチする正規表現
const rx_id1st = new RegExp('[a-zA-Z_\u00c0-\u1fff\u3040-\u318f\u3400-\u3d2d\u4e00-\u9fff\uf900-\ufaff]');
// 識別子の2文字目以降の文字にマッチする正規表現
const rx_id2nd = new RegExp('[a-zA-Z_\u00c0-\u1fff\u3040-\u318f\u3400-\u3d2d\u4e00-\u9fff\uf900-\ufaff0-9]');
// 数字と判定される文字にマッチする正規表現
const rx_num = new RegExp('[0-9]');
// 再帰下降構文解析で解析する。方針としてグローバル変数の
// inputStrに解析文字列を入れといて、基本1文字ずつ解析。
// その文字の位置を常にグローバル変数のidx変数に保存。
// 解析関数はinputStr.charAt(idx)から
// 解析を始める。その時に必要であれば解析が失敗することを
// 考慮してidxをidxBackupに保存しておく。もし、解析が成功
// したらidxは次に解析される文字の所まで進めておく義務がある。
// なおかつreturnで解析結果を返す。もし、解析が失敗したら
// エラーメッセージをグローバル変数のerrorMessageに書き込み、
// バックアップしていたidxBackupをidxに代入して戻しておく。
// なおかつreturnでnullを返すことにする。
let inputStr; // 解析する文字列(普通は\nが入った複数行の文字列)
let idx; // 現在解析しているinputStr中の一文字の場所
let errorMessage; // エラーメッセージを記録しておく場所
// 現在解析している場所の行番号と列番号を文字列で返す関数。
// エラーメッセージ作る時に使おう。
function lineColumnNo() {
let line = 1;
let column = 1;
for (let i=0;i<idx;i++) {
if (inputStr.charAt(i)==='\n') {
line++;
column = 1;
} else {
column++;
}
}
return ""+line+":"+column;
}
// 指定した1文字を検出する解析関数
function char(c) {
if (inputStr.charAt(idx)===c) {
idx++;
return c;
}
errorMessage = `Error(${lineColumnNo()}) ここには文字「${c}」がこなければなりません。`;
return null;
}
// コメントは「/*」で始まり「*/」で終るコメントのみ許可
function comment() {
const idxBackup = idx;
if (char('/')) {
if (char('*')) {
while (true) {
const idxBackup2 = idx;
if (char('*')) {
if (char('/')) {
return "comment";
}
}
idx = idxBackup2;
if (inputStr.charAt(idx)==='') {
idx = idxBackup;
errorMessage = `Error(${lineColumnNo()}) ここにはコメントがこなければなりません。`;
return null;
}
idx++;
}
}
}
idx = idxBackup;
errorMessage = `Error(${lineColumnNo()}) ここにはコメントがこなければなりません。`;
return null;
}
// 半角空白、タブ、改行の時成功。それ以外は失敗。
function whiteChar() {
if (char(' ')) return ' ';
if (char('\t')) return '\t';
if (char('\n')) return '\n';
errorMessage = `Error(${lineColumnNo()})`;
return null;
}
// whiteSpaceまたは空文字列を検出。なので絶対成功する。
// つまりスペースが入ってても良いし入ってなくても良い
// ような場所で使う。あとコメント文もwhiteSpace扱いに
// することにした。
function whiteSpace() {
while (true) {
const cmt = comment();
if (cmt)
continue;
const w = whiteChar();
if (w == null)
return "whiteSpace";
}
}
// 関数名、変数名などの名前に相当する識別子を検出する
function id() {
const idxBackup = idx;
let name = '';
let c = inputStr.charAt(idx);
if (!c.match(rx_id1st)) {
errorMessage = `Error(${lineColumnNo()}) ここには識別子がくるべきです。`;
return null;
}
name += c;
while (true) {
idx++;
c = inputStr.charAt(idx);
if (!c.match(rx_id2nd))
return name;
name += c;
}
}
// whiteSpaceを読み飛ばして入力文字列の終りを検出する
function eof() {
const idxBackup = idx;
const w = whiteSpace(); // 絶対成功する
const c = inputStr.charAt(idx); // idxが範囲外の時は空文字列
if (c === '')
return "EOF";
// ここまで来たら何か文字が残ってるのでエラー
idx = idxBackup;
errorMessage = `Error(${lineColumnNo()}): EOF(入力の最後でなければなりません。`;
return null;
}
// 文字列を解析する。
// まだ文字列中の引用符のエスケープとかには対応してない。
function string() {
const idxBackup = idx;
let a = char('"');
if (a==null)
a = char("'");
if (a==null) {idx=idxBackup;return null;}
let s = '';
while (true) {
const c = inputStr.charAt(idx);
if (c === '') {
idx = idxBackup;
errorMessage = `Error(${lineColumnNo()}) 文字列解析中に入力が終了しました。`;
return null;
}
if (c === a) {
idx++;
break;
}
s += c;
idx++;
}
return s;
}
// 数字を解析する。
function number() {
const idxBackup = idx;
let c = inputStr.charAt(idx);
if (c !== '.' && !c.match(rx_num)) {
errorMessage = `Error(${lineColumnNo()}) ここには数字がこなければなりません。`;
return null;
}
let num = '';
if (c === '.') { // '.'で始まる特殊な場合
idx++; c = inputStr.charAt(idx);
if (!c.match(rx_num)) {
idx = idxBackup;
return null;
}
idx = idxBackup;
c = inputStr.charAt(idx);
} else { // ここにくるのは整数部分
num += c;
idx++; c = inputStr.charAt(idx);
while (c.match(rx_num)) {
num += c;
idx++; c = inputStr.charAt(idx);
}
}
if (c === '.') { // 小数部分
num += c;
idx++; c = inputStr.charAt(idx);
while (c.match(rx_num)) {
num += c;
idx++; c = inputStr.charAt(idx);
}
}
if (c === 'e' || c === 'E') {
num += c;
idx++; c = inputStr.charAt(idx);
if (c === '+' || c === '-') {
num += c;
idx++; c = inputStr.charAt(idx);
}
while (c.match(rx_num)) {
num += c;
idx++; c = inputStr.charAt(idx);
}
}
return Number(num);
}
// 項(条件やビルトインなど)を解析。項は関数の形をしている。
function term() {
const idxBackup = idx;
const name = id();
if (name == null) return null;
whiteSpace();
if (char('(') == null) {idx=idxBackup;return null};
const args = []; // 引数の配列
while (true) {
whiteSpace();
if (inputStr.charAt(idx) === ',')
idx++;
whiteSpace();
const v = id(); if (v) {args.push(new Var(v));continue;} // 変数
const n = number(); if (n) {args.push(n);continue;} // 数字
const s = string(); if (s) {args.push(s);continue;} // 文字列
break;
}
whiteSpace();
if (char(')') == null) {idx=idxBackup;return null};
whiteSpace();
if (inputStr.charAt(idx) === ',')
idx++;
return new Term(name,args);
}
// ルール1本分。Ruleのインスタンスを返す。エラーならエラーメッセージを返す。
function rule() {
const idxBackup = idx;
whiteSpace();
const priority = number();
if (priority == null) { idx=idxBackup; return null; }
whiteSpace();
if (char(':') == null) { idx=idxBackup; return null; }
const lhs = [];
while (true) {
whiteSpace();
const cond = term(); // 1つの条件に該当
if (cond == null) break;
lhs.push(cond);
}
if (lhs.length === 0) {
errorMessage = `Error(${lineColumnNo()}) 条件部(LHS)がありません。`;
idx=idxBackup;
return null;
}
whiteSpace();
//if (char('-') == null) { idx=idxBackup; return null; } // やっぱり入れない
if (char('>') == null) { idx=idxBackup; return null; }
const rhs = [];
while (true) {
whiteSpace();
const com = term(); // 1つの実行命令に該当
if (com == null) break;
rhs.push(com);
}
if (rhs.length === 0) {
errorMessage = `Error(${lineColumnNo()}) 実行部(RHS)がありません。`;
idx=idxBackup;
return null;
}
whiteSpace();
if (char(';') == null) { idx=idxBackup; return null; }
return new Rule(priority,lhs,rhs);
}
// ルールの集合。ここが文法解析の出発点。
function rules() {
const idxBackup = idx;
const rs = [];
while (true) {
const e = eof();
if (e)
break; // 正常終了
whiteSpace();
const r = rule();
if (r) {
rs.push(r);
continue;
}
// ここまで来たら何かエラー
idx = idxBackup;
// errorMessageはrule()の時のエラーを残す
return null;
}
return rs;
}
// 文法解析を実行する関数。
// 解析するための文字列を引数で渡す。色々初期化してからスタート。
// 解析が成功したらRuleの配列を返す。そして、解析失敗の
// 時はエラーメッセージの文字列を返す。
// 入力が空っぽの時も成功と見なして空の配列を返す。
function parse(input) {
inputStr = input;
idx = 0;
errorMessage = "";
const rs = rules();
if (rs === null)
return errorMessage;
return rs;
}
export { parse };