2023年1月7日: Casdaq Perl 移植版のメモ

# 2023-01-07: Casdaq Perl 移植版のメモ

2023年1月7日 23時22分

Scheme で開発中の Casdaq を、わけあって Perl に移植した。移植したっていっても、足元で必要な機能に絞って移植しただけだから、Scheme 版のサブセットけど。その備忘録。

## Casdaq

Casdaq は Scheme で開発中のパターン整合系。文字列のパターンだけでなくて配列のパターンも（Scheme 版の場合はリストのパターンとベクターのパターンも）整合できる。一部で言語組み込みの機能をパターン中で使える。

## パターン

<a href="https://qiita.com/ugeKfMn7tfATKCW/items/bb77b31179ab48d7bb93">Scheme 版</a> から移植したパターン。

* ``AnyElem``（糖衣構文の ``_`` も）
* ``At``（``Enum``、``MinMax``、``Begin``、``End`` の全機能）
* ``AtBegin``（糖衣構文の ``^`` も）
* ``AtEnd``（糖衣構文の ``$`` も）
* ``Elem``（``Enum``、``MinMax``、``Pred`` の全機能）
* ``Opt``（``OptGre`` から改名）（別名の ``?`` も）
* ``Opt?``（``OptLaz`` から改名）（別名の ``??`` も）
* ``Ref``（別名の ``->`` も）
* ``Quant``（``RepGre`` から改名）（別名の ``{}``、部分機能の ``*``、``+`` も）
* ``Quant?``（``RepLaz`` から改名）（別名の ``{}?``、部分機能の ``*?``、``+?`` も）
* ``Selective``（別名の ``/`` も）
* ``Set!``（別名の ``<-`` も）
* ``SubSeq``（別名の ``...`` も）
* ``Sure``（別名の ``?=`` も）
* ``SureNot``（別名の ``?!`` も）
* ``String``
* ``String*``

Scheme 版から名前を換えたもの。

* ``Array``（Perl 文化に合わせて ``List`` から改名してみた、思い付きで）
* ``Array*``（Perl 文化に合わせて ``List`` から改名してみた、思い付きで）

このタイミングで新規に追加したもの。

* ``TypedElem``（本家 Scheme 版にも導入する予定）

## 関数

さしあたり必要だったものだけを移植した。置換の関数は今回は移植してない。

* ``match``
* ``match_entity``
* ``find``
* ``find_entity``
* ``findAll``
* ``findAll_entity``

## 使用例

### (1) "Hello, Casdaq!"

変数 ``$es`` に入ってる文字列 ``"Hello, Casdaq!"`` の中で名前の部分を後方参照変数 ``@name`` にいれる。正規表現の ``m/[^A-Za-z]([A-Za-z]+)/`` に相当するパターン（``$p``）を使う。

```Perl:
my $es = 'Hello, Casdaq!' ;

my $p =
  # /[^A-Za-z]([A-Za-z]+)/ に相当する Casdaq パターン
  [
  ['Elem', 0,
    ['MinMax', 'A', 'Z'],
    ['MinMax', 'a', 'z']],
  ['Set!', '@name',
    ['+',
      ['Elem',
        ['MinMax', 'A', 'Z'],
        ['MinMax', 'a', 'z']]]],
  ] ;

my $result = &CasdaqPerl::Match::find_entity ($p, $es) ;

my $entire = &CasdaqPerl::Result::getEntireRegion ($result) ;
say 'entire => "', $entire, '"' ;

foreach my $mapEntry (@{&CasdaqPerl::Result::getSubRegionMap($result)})
  {
  my ($name, $value) = @{$mapEntry} ;
  say $name, ' => "', $value, '"'
  }
```

実行結果は期待どおり。

```
entire => " Casdaq"
@name => "Casdaq"
```


### (2) ログファイルのセクション

配列のパターン整合の例。ログファイルの中身は行単位で配列 ``$es`` 入ってる。

```Perl:
my $es =
  [
  '################################################################################',
  '# CRC Results',
  '################################################################################',
  '',
  'PP1.S.Max',
  'NL1.S.Min',
  '',
  '################################################################################',
  '# QVR Results',
  '################################################################################',
  '',
  'Correct',
  'Mutual Connect: None',
  ''
  ] ;
```

セクション名を後方参照変数 ``@subtitle`` に、セクションの中身（複数行）を後方参照変数 ``@body`` に入れるパターン ``$p`` はこんな。

```Perl:
my $p =
  [
  ['*',
    ['String', ['{}', [80,80], '"#"']],
    ['String', '"# "', ['Set!', '@subtitle', ['+', '_']]],
    ['String', ['{}', [80,80], '"#"']],
    ['Set!', '@body',
      ['*',
        ['SureNot', ['String', ['{}', [80,80], '"#"']]],
        ['String*']
      ]
    ]
  ]
  ] ;
```

このパターンで ``find_entity`` 関数をつかって、整合した部分シーケンスを取り出す。整合した回数が複数だとしても全部の結果を得られるあたりは、Perl の正規表現の整合とは違うとこだ。

```Perl:
my $result = &CasdaqPerl::Match::find_entity ($p, $es) ;
foreach my $mapEntry (@{&CasdaqPerl::Result::getSubRegionMap($result)})
  {
  my ($name, $value) = @{$mapEntry} ;
  if (ref ($value) eq 'ARRAY')
    {say $name, ' => [', join (', ', map ({qq("$_")} @{$value})), ']'}
  else
    {say $name, ' => "', $value, '"'}
  }
```

実行結果は期待どおり。Scheme 版とも一致。

```
@subtitle => "CRC Results"
@body => ["", "PP1.S.Max", "NL1.S.Min", ""]
@subtitle => "QVR Results"
@body => ["", "Correct", "Mutual Connect: None", ""]
```

### (3) 条件を満たすレコードの抽出

入れ子になった配列のパターン整合の例。名前と連絡先と好きな食べ物の情報が（この順番で）入ってる配列構造のレコードが複数入ってる配列 ``$es`` から、条件を満たす人の名前を抽出して後方参照変数 ``@AppleDevotee`` に入れる。条件は好きな食べ物の中に ``apple`` であるか ``apple-`` で始まるものが入ってるもの。

```Perl:
my $es =
  [
    [
    ['name','Yves Leroy'],
    ['e-mail','yves.leroy@oubrier.fr'],
    ['favorite','apple-cider'],
    ['favorite','pizza'],
    ['favorite','vodka'],
    ],

    [
    ['name','Leighton Faulkner'],
    ['e-mail','faulkner@buy-more.uk'],
    ['favorite','fried-chicken'],
    ['favorite','grilled-lamb'],
    ['favorite','olive'],
    ],

    [
    ['name','Phillipe Chantong'],
    ['e-mail','phch@cliquez-ici.fr'],
    ['facebook','phillpe95c'],
    ['favorite','jasmine-tea'],
    ['favorite','bagle'],
    ['favorite','sushi'],
    ],

    [
    ['name','Jurgen Wagner'],
    ['e-mail','mw@gruenehaus.de'],
    ['twitter','jurgen1997'],
    ['favorite','licorice'],
    ['favorite','beer'],
    ['favorite','apple-pie'],
    ['favorite','green-tea'],
    ],
  ] ;
```

抽出用のパターンはこれ。該当するレコードと該当するレコードの間に該当しないレコードが挟まる可能性があるから、検索したいレコードが整合するパターンと該当しないレコードが整合するためのパターンを ``/``（``Selective``）でつなぐ。そして ``/`` 全体を ``*`` で繰り返す。

```Perl:
my $p =
  [
  ['*',
    ['/',
      [['Array*',
        ['Array',
          ['String', '"name"'],
          ['String', ['Set!', '@AppleDevotee', ['*', '_']]]],
        ['*?', '_'],
        ['Array',
          ['String', '"favorite"'],
          ['String', '"apple"', ['?', '"-"', ['*', '_']]]]]],
      ['_']]]
  ] ;
```

抽出実行するコードはこれ。

```Perl:
my $result = &CasdaqPerl::Match::find_entity ($p, $es) ;
foreach my $mapEntry (@{&CasdaqPerl::Result::getSubRegionMap($result)})
  {
  my ($name, $value) = @{$mapEntry} ;
  say $name, ' => "', $value, '"'
  }
```

結果は期待どおり。

```
@AppleDevotee => "Yves Leroy"
@AppleDevotee => "Jurgen Wagner"
```

### 変化形

``[Elem [Pred ...]]`` パターンを使えば配列の要素の文字列パターンの整合には Scheme 版と同じよおに言語の関数を使える。その確認。上の例の「好きな食べ物の中に ``apple`` であるか ``apple-`` で始まるものが入ってるもの」のパターンを Perl 組み込みの正規表現整合の仕組みを使ってやってみた例。下から3行目の ``[Elem [Pred ...]]`` がそれ。

```Perl:
my $p =
  [
  ['*',
    ['/',
      [['Array*',
        ['Array',
          ['String', '"name"'],
          ['String', ['Set!', '@AppleDevotee', ['*', '_']]]],
        ['*?', '_'],
        ['Array',
          ['String', '"favorite"'],
          ['Elem', ['Pred', sub {local($_)=@_ ; m/^apple(-.*)?$/}]]]]],
      ['_']]]
  ] ;
```

結果は当然同じ。

```
@AppleDevotee => "Yves Leroy"
@AppleDevotee => "Jurgen Wagner"
```

## 最後に

### パターン名称・糖衣構文

Scheme 版 Casdaq から Perl 版を作って、記号の見直しをしなきゃなって思った。Perl に移植した時点で JSON を意識しながら、Scheme の S式も考えて...。

例えば、``AnyElem`` の記号は移植前は「``@``」にしてたけど「``_``」にした。正規表現的には「``.``」なのけど、Scheme だと「``.``」は使いにくいし、Scheme マクロで「任意の一つ」を意味する記号と同じ意味になるから、これがいいかなって。

### 順番不問で整合するパターン

これは拡張案の一つけど、Scheme 版の時点で懸案の機能の「順番不問の整合」をするパターンについては、Perl だとハッシュを普通に多様するから、差し迫った問題だなって思った。（これについては<a href="https://qiita.com/ugeKfMn7tfATKCW/items/890ecfd07b3cfa4cac11">だいぶ前にアイデアを考えた</a>けど、その方式を実装するには大改造が必要で、やる気がしない...）

### 未移植の機能のこと

今回は Perl 版に移植しなかった（移植が途中の）``Let*-Expr``、``LetRec*-Expr``、``Let-Comparator`` だとかのパターンとか、``replace``、``replaceAll`` 関数とか、パーサーとしての機能なんかも、今後 Perl 版にも移植したいかも。でもまずは本家の Scheme 版で仕様をあるていど固めてからになるともうけど。

今後も Scheme 版、Perl 版とも一緒に開発を進めるつもりだから、使いやすいパターンを追加したり、仕様変更したりすることもあるかも。