pegase 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pegase/LICENSE +7 -0
- pegase/LICENSE.jp +9 -0
- pegase/README.md +128 -0
- pegase/__init__.py +6 -0
- pegase/flatten.py +22 -0
- pegase/grammar.py +166 -0
- pegase/nonterminal.py +44 -0
- pegase/peg.peg +36 -0
- pegase/peg.py +240 -0
- pegase/pegase_gen_R.py +62 -0
- pegase/primitive.py +377 -0
- pegase-1.0.0.dist-info/METADATA +107 -0
- pegase-1.0.0.dist-info/RECORD +15 -0
- pegase-1.0.0.dist-info/WHEEL +5 -0
- pegase-1.0.0.dist-info/top_level.txt +1 -0
pegase/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright © 2025 Shinichiro Ikeda
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
pegase/LICENSE.jp
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2025 池田真一郎
|
|
4
|
+
|
|
5
|
+
本ソフトウェアおよび関連する文書のファイル(以下「ソフトウェア」)の複製を取得した全ての人物に対し、以下の条件に従うことを前提に、ソフトウェアを無制限に扱うことを無償で許可します。これには、ソフトウェアの複製を使用、複製、改変、結合、公開、頒布、再許諾、および/または販売する権利、およびソフトウェアを提供する人物に同様の行為を許可する権利が含まれますが、これらに限定されません。
|
|
6
|
+
|
|
7
|
+
上記の著作権表示および本許諾表示を、ソフトウェアの全ての複製または実質的な部分に記載するものとします。
|
|
8
|
+
|
|
9
|
+
ソフトウェアは「現状有姿」で提供され、商品性、特定目的への適合性、および権利の非侵害性に関する保証を含むがこれらに限定されず、明示的であるか黙示的であるかを問わず、いかなる種類の保証も行われません。著作者または著作権者は、契約、不法行為、またはその他の行為であるかを問わず、ソフトウェアまたはソフトウェアの使用もしくはその他に取り扱いに起因または関連して生じるいかなる請求、損害賠償、その他の責任について、一切の責任を負いません。
|
pegase/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Pegase
|
|
2
|
+
Parsing Expression Grammar Abstract Syntax Extension
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## License
|
|
6
|
+
MIR License
|
|
7
|
+
|
|
8
|
+
### [英語]
|
|
9
|
+
https://opensource.org/license/mit
|
|
10
|
+
|
|
11
|
+
### [日本語]
|
|
12
|
+
https://licenses.opensource.jp/MIT/MIT.html
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Author
|
|
16
|
+
Shinichiro Ikeda
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## 概要
|
|
20
|
+
Pegaseは、マサチューセッツ工科大学(Massachusetts Institute of Technology)のBryan Fordが POPL 2004 (ACM SIGPLAN - SIGACT Symposium on Principles of Programming Languages)で発表したParsing Expression Grammars:
|
|
21
|
+
A Recognition-Based Syntactic Foundationに基づき、構文解析モジュールとしてPythonで実装したものです。
|
|
22
|
+
PEGの論文はhttps://bford.info/pub/lang/peg/で見ることができます。
|
|
23
|
+
|
|
24
|
+
Pegaseの文法オブジェクト(G)は、外部から与えられた構文規則(Syntax Rule)および非終端記号オブジェクト(Nonterminals)から文法(Grammar)を定義し、その文法オブジェクトのparseメソッドで構文解析を実行する機能を持ちます。
|
|
25
|
+
構文解析の結果は非終端記号オブジェクトによる構文解析木(Syntax Tree)として構築して出力します。
|
|
26
|
+
|
|
27
|
+
PEG文法で記述された構文定義を解析する際は、Pegase内部に用意されたPEG準拠の構文規則および非終端記号オブジェクトを用いて、構文解析の実行および解析結果の構文規則を得ることができます。
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## ファイル構成
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
LICENSE MIT License (英語)
|
|
34
|
+
LICENSE.jp MIT License (日本語)
|
|
35
|
+
README.md 本ファイル
|
|
36
|
+
__init__.py package定義ファイル
|
|
37
|
+
grammar.py 文法オブジェクトクラスファイル
|
|
38
|
+
primitive.py プリミティブ処理ファイル
|
|
39
|
+
peg.py PEG文法の構文解析用VNおよびRの定義ファイル
|
|
40
|
+
peg.peg PEG文法定義ファイル(参考資料:peg.pyの元となったファイル。構文解析の実行には必要ありません)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 使用方法
|
|
44
|
+
Pegaseの文法オブジェクトは、pegaseパッケージのGクラスのインスタンスを生成することで作成します。文法オブジェクトの生成時には、パラメータに非終端記号オブジェクトを定義したパッケージ名(VN)、構文規則を定義したS式(R)、構文解析の起点となるS式の名前(eS)を指定します。このパラメータを省略した場合、pegaseパッケージの内部に持つPEG文法解析用のVN、R、およびeSを使用します。
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
from pegase import G
|
|
48
|
+
PEG = G()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
ソースコードを記述するプログラミング言語の文法定義(PEG文法で記述されたもの)を、このPEG文法オブジェクト(PEG)を用いて構文解析することで抽象構文木を取得し、それを評価することでプログラミング言語用の構文規則(R)を得ることができます。
|
|
52
|
+
|
|
53
|
+
ここでは例として、四則演算の機能を持つ言語を定義し、`basic-calc.peg`として保存します。
|
|
54
|
+
|
|
55
|
+
```basic-calc.peg
|
|
56
|
+
expr <- term (('+' / '-') term)*
|
|
57
|
+
term <- factor (('*' / '/') factor)*
|
|
58
|
+
factor <- [0-9]+ / '(' expr ')'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
この文法定義ファイル(basic-calc.peg)を先ほど作成したPEG文法オブジェクトのparseメソッドを使用して解析し、解析結果の抽象構文木(ST)を評価することでbasic-calc.pegの構文規則(R)を取得します。
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
basic_ST = PEG.parse('basic-calc.peg')
|
|
65
|
+
basic_R = basic_ST.evaluate()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
抽象構文木の各ノード毎の評価処理は、PEG文法の非終端記号名をクラスとしたPythonパッケージとして記述します。ここで作成する四則演算の例では、expr、term、factorのクラスを作成し、非終端記号オブジェクトとします。
|
|
69
|
+
|
|
70
|
+
四則演算の機能を持つ言語の非終端記号オブジェクトのPythonパッケージを`basic-calc_VN.py`として作成します。
|
|
71
|
+
|
|
72
|
+
```python:basic-calc_VN.py
|
|
73
|
+
from functools import reduce
|
|
74
|
+
from pegase import A
|
|
75
|
+
|
|
76
|
+
def calc(x,opcode,y):
|
|
77
|
+
match opcode:
|
|
78
|
+
case '+':
|
|
79
|
+
return x + y
|
|
80
|
+
case '-':
|
|
81
|
+
return x- y
|
|
82
|
+
case '*':
|
|
83
|
+
return x * y
|
|
84
|
+
case '/':
|
|
85
|
+
return x / y
|
|
86
|
+
|
|
87
|
+
class _expr(A):
|
|
88
|
+
def evaluate(self):
|
|
89
|
+
return reduce(lambda x,y:calc(x,y[0],y[1].evaluate()),[('+',self.value[0])] if len(self.value) == 1 else [('+',self.value[0])] + list(zip(self.value[1::2],self.value[2::2])),0)
|
|
90
|
+
|
|
91
|
+
class _term(A):
|
|
92
|
+
def evaluate(self):
|
|
93
|
+
return reduce(lambda x,y:calc(x,y[0],y[1].evaluate()),[('+',self.value[0])] if len(self.value) == 1 else [('+',self.value[0])] + list(zip(self.value[1::2],self.value[2::2])),0)
|
|
94
|
+
|
|
95
|
+
class _factor(A):
|
|
96
|
+
def evaluate(self):
|
|
97
|
+
match self.value[0]:
|
|
98
|
+
case '(':
|
|
99
|
+
return self.value[1].evaluate()
|
|
100
|
+
case _:
|
|
101
|
+
return int(''.join(self.value))
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
非終端記号オブジェクト名、構文規則、構文解析の起点名を元に四則演算言語の文法オブジェクトを生成し、その文法オブジェクトのparseメソッドを用いて数式を解析した結果を評価することで、四則演算の結果を得ます。
|
|
106
|
+
|
|
107
|
+
四則演算のメインプログラムを`basic-calc.py`として作成します。
|
|
108
|
+
|
|
109
|
+
```python:basic-calc.py
|
|
110
|
+
import sys
|
|
111
|
+
from pegase import G
|
|
112
|
+
|
|
113
|
+
if __name__ == '__main__':
|
|
114
|
+
PEG = G()
|
|
115
|
+
basic_ST = PEG.parse('basic-calc.peg')
|
|
116
|
+
basic_R = basic_ST.evaluate()
|
|
117
|
+
basic_G = G('basic-calc_VN',basic_R,'expr')
|
|
118
|
+
result = basic_G.parse(sys.argv[1]).evaluate()
|
|
119
|
+
print(result)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
実行結果は以下の通りです。
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
$ python basic-calc.py '1+2*3/(4-5)'
|
|
126
|
+
-5.0
|
|
127
|
+
```
|
|
128
|
+
|
pegase/__init__.py
ADDED
pegase/flatten.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Copyright © 2025 Shinichiro Ikeda
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
#
|
|
8
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
#
|
|
10
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# flatten(source [,exclude]) -- n次元の配列を1次元配列に変換する
|
|
14
|
+
# - strは展開しない
|
|
15
|
+
# - excludeで指定したクラスは展開しない
|
|
16
|
+
# - excludeはクラス単体またはクラスのリストで指定する
|
|
17
|
+
# 例)
|
|
18
|
+
# flatten(x) str以外の__iter__オブジェクトを展開する
|
|
19
|
+
# flatten(x,tuple) tupleを展開対象外にする
|
|
20
|
+
# flatten(x,[tuple,dict]) tuple,dictを展開対象外にする
|
|
21
|
+
|
|
22
|
+
flatten = lambda x,ts=[],f=lambda x,ts,g: [z for y in x for z in (g(y,ts,g) if hasattr(y, '__iter__') and not any([isinstance(y,t) for t in (ts+[str] if isinstance(ts,list) else [ts,str])]) else (y,))]: f(x,ts,f)
|
pegase/grammar.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Copyright © 2025 Shinichiro Ikeda
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
#
|
|
8
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
#
|
|
10
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
11
|
+
|
|
12
|
+
import importlib
|
|
13
|
+
import inspect
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from functools import reduce
|
|
18
|
+
|
|
19
|
+
from .nonterminal import A
|
|
20
|
+
from .peg import peg_R
|
|
21
|
+
from .primitive import Stream,_expression,DefinitionError,ParseError
|
|
22
|
+
from .flatten import flatten
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Definition
|
|
26
|
+
# G = (VN,VT,R,eS)
|
|
27
|
+
# A ∈ VN
|
|
28
|
+
# a ∈ VT
|
|
29
|
+
# r ∈ R, A <- e ∈ R
|
|
30
|
+
# e = R[A]
|
|
31
|
+
|
|
32
|
+
# Usage:
|
|
33
|
+
# PEG = G()
|
|
34
|
+
# target_R = PEG.parse('target.peg').evaluate()
|
|
35
|
+
# target_eS = 'TargetStartExpr'
|
|
36
|
+
# result = G(target_VN,target_R,target_eS).parse('source-file')
|
|
37
|
+
|
|
38
|
+
# from pegase import G
|
|
39
|
+
# querre_VN = 'querre_VN'
|
|
40
|
+
# querre_eS = 'direct_SQL_statement'
|
|
41
|
+
# result = G(querre_VN,G().parse('querre.peg').evaluate(),querre_eS).parse('select * from "a.csv";').evaluate()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# PEG = G()
|
|
45
|
+
# ebnf_VN = 'ebnf_VN'
|
|
46
|
+
# ebnf_R = PEG.parse('ebnf.peg').evaluate()
|
|
47
|
+
# ebnf_eS = 'Grammar'
|
|
48
|
+
# querre_VN = 'querre_VN'
|
|
49
|
+
# querre_R = G(ebnf_VN,ebnf_R,ebnf_eS).parse('querre.ebnf').evaluate()
|
|
50
|
+
# querre_eS = 'direct_SQL_statement'
|
|
51
|
+
# result = G(querre_VN,querre_R,querre_eS).parse('select * from "a.csv";').evaluate()
|
|
52
|
+
|
|
53
|
+
# result = G('querre_VN',G('ebnf_VN',G().parse('ebnf.peg').evaluate(),'Grammar').parse('querre.ebnf').evaluate(),'direct_SQL_statement').parse('select * from "a.csv";').evaluate()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# G.grammar(pegfile,eS,VN) コンストラクタ使用
|
|
57
|
+
# result = G.grammar('querre.peg','direct_SQL_statement','querre_VN').parse('select * from "a.csv";').evaluate()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
#================================================================================
|
|
61
|
+
# G Class
|
|
62
|
+
# parameters:
|
|
63
|
+
# VN A ∈ VN Aをクラスとして実装したVNモジュール (省略した場合はpeg_VNを使用)
|
|
64
|
+
# パッケージから呼び出す場合は('package','.module')のようにtupleで指定する (モジュール名は相対表示)
|
|
65
|
+
# R A <- e ∈ R 解析ルール(A <- e)の一覧を{'A':e}として実装したdict (省略した場合はpeg_Rを使用)
|
|
66
|
+
# eS e = R(A) 解析を開始するexpressionをAとして指定 (省略した場合はpeg_VN.Grammarを使用)
|
|
67
|
+
#================================================================================
|
|
68
|
+
class G:
|
|
69
|
+
def __init__(self,VN=None,R=None,eS=None,verbose=0):
|
|
70
|
+
R = R or peg_R
|
|
71
|
+
# eSが指定されなかった場合はRに定義された最初の項目をeSとして使用する
|
|
72
|
+
eS = eS or list(R.keys())[0]
|
|
73
|
+
self.verbose = verbose
|
|
74
|
+
# # verboseが0の場合はtracebackを表示しない
|
|
75
|
+
# if self.verbose == 0:
|
|
76
|
+
# sys.tracebacklimit=0
|
|
77
|
+
# モジュールVNに定義されているクラス一覧をdictとして取得
|
|
78
|
+
self.VN = dict(inspect.getmembers(importlib.import_module('.peg',package=__package__) if VN is None else (importlib.import_module(VN[1],package=VN[0]) if isinstance(VN,tuple) else importlib.import_module(VN)),inspect.isclass))
|
|
79
|
+
parent_VN = 'peg' if VN is None else (VN[1].split('.')[-1] if isinstance(VN,tuple) else VN)
|
|
80
|
+
self.parent = (self.VN[parent_VN],) if parent_VN in self.VN else (A,)
|
|
81
|
+
self.R = {A:_expression(e) for A,e in R.items()}
|
|
82
|
+
self.eS = eS
|
|
83
|
+
if self.eS not in self.R.keys():
|
|
84
|
+
raise DefinitionError(f'eS not defined in R: {self.eS}')
|
|
85
|
+
|
|
86
|
+
# ----------------------------
|
|
87
|
+
# 未定義の非終端記号参照の検出
|
|
88
|
+
# ----------------------------
|
|
89
|
+
nonterminals = list(R.keys())
|
|
90
|
+
|
|
91
|
+
rules = list(set(flatten(
|
|
92
|
+
reduce(
|
|
93
|
+
lambda rules,definition:
|
|
94
|
+
rules + (
|
|
95
|
+
lambda expr,
|
|
96
|
+
f=lambda ex,g: [ex[1]] if ex[0] == '_rules' else [g(e,g) for e in ex[1:] if e[0] in ['_optional','_zero_or_more','_one_or_more','_and_predicate','_not_predicate','_sequence','_prioritized_choice','_rules']]
|
|
97
|
+
: f(expr,f)
|
|
98
|
+
)(definition[2]),
|
|
99
|
+
R.values(),
|
|
100
|
+
[]
|
|
101
|
+
)
|
|
102
|
+
))) + [eS]
|
|
103
|
+
|
|
104
|
+
unused_rules = sorted(set(nonterminals) - set(rules))
|
|
105
|
+
if len(unused_rules) > 0 and self.verbose > 0:
|
|
106
|
+
print(f'WARNING: Unuse rules detected: {unused_rules}')
|
|
107
|
+
|
|
108
|
+
undefined_rules = sorted(set(rules) - set(nonterminals))
|
|
109
|
+
if len(undefined_rules) > 0:
|
|
110
|
+
raise ParseError(f'Undefined rules detected: {undefined_rules}')
|
|
111
|
+
# if len(undefined_rules) > 0 and self.verbose > 0:
|
|
112
|
+
# print(f'WARNING: Undefined rules detected: {undefined_rules}')
|
|
113
|
+
|
|
114
|
+
# --------------------
|
|
115
|
+
# left recursionの検出
|
|
116
|
+
# --------------------
|
|
117
|
+
# 各ルール毎に最初の_rulesプリミティブを抽出(最初が終端記号の場合を除く)
|
|
118
|
+
leading_rules = {definition[1]: flatten((lambda expr,f=lambda ex,g:
|
|
119
|
+
# sequenceはe1のみ抽出対象とする
|
|
120
|
+
[] if ex[0] in ['_empty_string','_literal_string','_character_class','_any_character'] else
|
|
121
|
+
[ex[1]] if ex[0] == '_rules' else
|
|
122
|
+
[g(e,g) for e in ex[1:] if e[0] in ['_optional','_zero_or_more','_one_or_more','_and_predicate','_not_predicate','_sequence','_prioritized_choice','_rules']] if ex[0] == '_prioritized_choice' else
|
|
123
|
+
[g(e,g) for e in [ex[1]] if e[0] in ['_optional','_zero_or_more','_one_or_more','_and_predicate','_not_predicate','_sequence','_prioritized_choice','_rules']]
|
|
124
|
+
: f(expr,f)
|
|
125
|
+
)(definition[2]))
|
|
126
|
+
for definition in R.values()
|
|
127
|
+
}
|
|
128
|
+
# 自分の配下で自分を呼び出す定義を左再帰として検出
|
|
129
|
+
left_recursions = {A:e for A,e in
|
|
130
|
+
{A: flatten(
|
|
131
|
+
(lambda A,e,f=lambda A,a,ex,g: [g(A+[e],e,leading_rules[e],g) if e in leading_rules and e not in A else (f'{a} <- {e}' if e == A[0] else []) for e in ex]: f([A],None,e,f))(A,e)
|
|
132
|
+
) for A,e in leading_rules.items()
|
|
133
|
+
}.items()
|
|
134
|
+
if len(e) > 0
|
|
135
|
+
}
|
|
136
|
+
if len(left_recursions) > 0:
|
|
137
|
+
raise DefinitionError(f'left recursion detected',left_recursions)
|
|
138
|
+
|
|
139
|
+
def __repr__(self):
|
|
140
|
+
return '{1}.{0.__class__.__name__}(\n VN = {2},\n R = {3},\n eS = {4}\n)'.format(self,__package__,list(self.VN.keys()),list(self.R.keys()),self.eS)
|
|
141
|
+
|
|
142
|
+
def parse(self,src,forcefile=False):
|
|
143
|
+
if os.path.isfile(src):
|
|
144
|
+
with open(src,'r') as f:
|
|
145
|
+
stream = f.read()
|
|
146
|
+
elif forcefile:
|
|
147
|
+
raise ParseError(f'File not found: {src}')
|
|
148
|
+
else:
|
|
149
|
+
stream = src
|
|
150
|
+
|
|
151
|
+
s = Stream(stream,self)
|
|
152
|
+
# eSと!.のsequenceを作成して実行する
|
|
153
|
+
parsed = _expression(('_sequence',('_rules',self.eS),('_not_predicate',('_any_character','.'))))(s)
|
|
154
|
+
|
|
155
|
+
if parsed is None:
|
|
156
|
+
raise ParseError('Parse error detected at line #{}: {}'.format(len(re.findall('\n',s._stream[:s._hwm]))+1,s._stream[s._stream[:s._hwm].rfind('\n')+1:s._hwm]),s)
|
|
157
|
+
|
|
158
|
+
return parsed[0]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# G.grammar(pegfile,eS,VN) コンストラクタ
|
|
162
|
+
@classmethod
|
|
163
|
+
def grammar(cls,pegfile,eS=None,VN=None,verbose=0):
|
|
164
|
+
return cls(VN,G().parse(pegfile,forcefile=True).evaluate(),eS,verbose=verbose)
|
|
165
|
+
|
|
166
|
+
# EOF
|
pegase/nonterminal.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Copyright © 2025 Shinichiro Ikeda
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
#
|
|
8
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
#
|
|
10
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
11
|
+
|
|
12
|
+
from functools import reduce
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _format_ast(ast,indent=0):
|
|
16
|
+
if len(ast.value) == 1 and isinstance(ast.value[0],str):
|
|
17
|
+
result = f'{" "*indent}{ast.__class__.__name__}({repr(ast.value[0])})\n'
|
|
18
|
+
else:
|
|
19
|
+
# result = f'{" "*indent}{ast.__class__.__name__}(\n' + \
|
|
20
|
+
# reduce(lambda x,n: x + f'{" "*(indent+4)}{repr(n)}\n' if isinstance(n,str) else x + _format_ast(n,indent+4), [''.join(ast.value)] if len(ast.value) > 0 and all([isinstance(v,str) for v in ast.value]) else ast.value, '') + \
|
|
21
|
+
# f'{" "*indent})\n'
|
|
22
|
+
result = f'{" "*indent}{ast.__class__.__name__}(\n' + \
|
|
23
|
+
reduce(lambda x,n: x + f'{" "*(indent+4)}{repr(n)}\n' if isinstance(n,str) else x + _format_ast(n,indent+4), ast.value, '') + \
|
|
24
|
+
f'{" "*indent})\n'
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
#================================================================================
|
|
29
|
+
# A Class
|
|
30
|
+
#================================================================================
|
|
31
|
+
class A:
|
|
32
|
+
def __init__(self,value):
|
|
33
|
+
self.value = value
|
|
34
|
+
|
|
35
|
+
def __repr__(self):
|
|
36
|
+
return f'{self.__class__.__name__}({repr(self.value)})'
|
|
37
|
+
|
|
38
|
+
def evaluate(self):
|
|
39
|
+
return reduce(lambda x,y: x + y if isinstance(y,str) else x + y.evaluate(),self.value,'')
|
|
40
|
+
|
|
41
|
+
def format(self):
|
|
42
|
+
return _format_ast(self)[:-1]
|
|
43
|
+
|
|
44
|
+
# EOF
|
pegase/peg.peg
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Hierarchial syntax
|
|
2
|
+
Grammar <- Spacing Definition+ EndOfFile
|
|
3
|
+
Definition <- Identifier LEFTARROW Expression
|
|
4
|
+
|
|
5
|
+
Expression <- Sequence (SLASH Sequence)*
|
|
6
|
+
Sequence <- Prefix+
|
|
7
|
+
Prefix <- (AND / NOT)? Suffix
|
|
8
|
+
Suffix <- Primary (QUESTION / STAR / PLUS)?
|
|
9
|
+
Primary <- Identifier !LEFTARROW / OPEN Expression CLOSE / Literal / Class / DOT
|
|
10
|
+
|
|
11
|
+
# Lexical syntax
|
|
12
|
+
Identifier <- IdentStart IdentCont* Spacing
|
|
13
|
+
IdentStart <- [a-zA-Z_]
|
|
14
|
+
IdentCont <- IdentStart / [0-9]
|
|
15
|
+
|
|
16
|
+
Literal <- ['] (!['] Char)* ['] Spacing / ["] (!["] Char)* ["] Spacing
|
|
17
|
+
Class <- '[' (!']' Range)* ']' Spacing
|
|
18
|
+
Range <- Char '-' Char / Char
|
|
19
|
+
Char <- '\\' [nrt'"\[\]\\] / '\\' [0-2][0-7][0-7] / '\\' [0-7][0-7]? / !'\\' .
|
|
20
|
+
|
|
21
|
+
LEFTARROW <- '<-' Spacing
|
|
22
|
+
SLASH <- '/' Spacing
|
|
23
|
+
AND <- '&' Spacing
|
|
24
|
+
NOT <- '!' Spacing
|
|
25
|
+
QUESTION <- '?' Spacing
|
|
26
|
+
STAR <- '*' Spacing
|
|
27
|
+
PLUS <- '+' Spacing
|
|
28
|
+
OPEN <- '(' Spacing
|
|
29
|
+
CLOSE <- ')' Spacing
|
|
30
|
+
DOT <- '.' Spacing
|
|
31
|
+
|
|
32
|
+
Spacing <- (Space / Comment)*
|
|
33
|
+
Comment <- '#' (!EndOfLine .)* EndOfLine
|
|
34
|
+
Space <- ' ' / '\t' / EndOfLine
|
|
35
|
+
EndOfLine <- '\r\n' / '\n' / '\r'
|
|
36
|
+
EndOfFile <- !.
|
pegase/peg.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Copyright © 2025 Shinichiro Ikeda
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
#
|
|
8
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
#
|
|
10
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
11
|
+
|
|
12
|
+
from functools import reduce
|
|
13
|
+
from .nonterminal import A
|
|
14
|
+
from .primitive import ParseError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#================================================================================
|
|
18
|
+
# Persing Expression Grammar VN
|
|
19
|
+
#================================================================================
|
|
20
|
+
class peg(A):
|
|
21
|
+
def preprocessing(self):
|
|
22
|
+
self.value = [v if isinstance(v,str) else v.preprocessing() for v in self.value if not isinstance(v,_Spacing)]
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
#--------------------------------------------------------------------------------
|
|
26
|
+
class _Grammar(peg):
|
|
27
|
+
# Grammar <- Spacing Definition+ EndOfFile
|
|
28
|
+
def evaluate(self):
|
|
29
|
+
return dict([d.evaluate() for d in self.value if isinstance(d,_Definition)])
|
|
30
|
+
|
|
31
|
+
class _Definition(peg):
|
|
32
|
+
# Definition <- Identifier LEFTARROW Expression
|
|
33
|
+
def evaluate(self):
|
|
34
|
+
return (self.value[0].evaluate()[1],('_definition',self.value[0].evaluate()[1],self.value[2].evaluate()))
|
|
35
|
+
|
|
36
|
+
class _Expression(peg):
|
|
37
|
+
# Expression <- Sequence (SLASH Sequence)*
|
|
38
|
+
def evaluate(self):
|
|
39
|
+
return ('_prioritized_choice',*[s.evaluate() for s in self.value[::2]]) if len(self.value) > 1 else self.value[0].evaluate()
|
|
40
|
+
|
|
41
|
+
class _Sequence(peg):
|
|
42
|
+
# Sequence <- Prefix*
|
|
43
|
+
def evaluate(self):
|
|
44
|
+
s = [s.evaluate() for s in self.value]
|
|
45
|
+
return ('_sequence',*s) if len(s) > 1 else s[0]
|
|
46
|
+
|
|
47
|
+
class _Prefix(peg):
|
|
48
|
+
# Prefix <- (AND / NOT)? Suffix
|
|
49
|
+
def evaluate(self):
|
|
50
|
+
match self.value[0]:
|
|
51
|
+
case _AND():
|
|
52
|
+
return ('_and_predicate',self.value[1].evaluate())
|
|
53
|
+
case _NOT():
|
|
54
|
+
return ('_not_predicate',self.value[1].evaluate())
|
|
55
|
+
case _:
|
|
56
|
+
# Just Suffix:
|
|
57
|
+
return self.value[0].evaluate()
|
|
58
|
+
|
|
59
|
+
class _Suffix(peg):
|
|
60
|
+
# Suffix <- Primary (QUESTION / STAR / PLUS)?
|
|
61
|
+
def evaluate(self):
|
|
62
|
+
if len(self.value) > 1:
|
|
63
|
+
match self.value[1]:
|
|
64
|
+
case _QUESTION():
|
|
65
|
+
return ('_optional',self.value[0].evaluate())
|
|
66
|
+
case _STAR():
|
|
67
|
+
return ('_zero_or_more',self.value[0].evaluate())
|
|
68
|
+
case _PLUS():
|
|
69
|
+
return ('_one_or_more',self.value[0].evaluate())
|
|
70
|
+
case _:
|
|
71
|
+
raise ParseError(f'Unrecognized suffix: {self.value[1]}')
|
|
72
|
+
|
|
73
|
+
return self.value[0].evaluate()
|
|
74
|
+
|
|
75
|
+
class _Primary(peg):
|
|
76
|
+
# Primary <- Identifier !LEFTARROW / OPEN Expression CLOSE / Literal / Class / DOT
|
|
77
|
+
def evaluate(self):
|
|
78
|
+
match self.value[0]:
|
|
79
|
+
case _Identifier():
|
|
80
|
+
return self.value[0].evaluate()
|
|
81
|
+
case _OPEN():
|
|
82
|
+
assert isinstance(self.value[2],_CLOSE),'ParseError, "(" was never closed:'+str(self.value)
|
|
83
|
+
return self.value[1].evaluate()
|
|
84
|
+
case _Literal():
|
|
85
|
+
return self.value[0].evaluate()
|
|
86
|
+
case _Class():
|
|
87
|
+
return self.value[0].evaluate()
|
|
88
|
+
case _DOT():
|
|
89
|
+
return self.value[0].evaluate()
|
|
90
|
+
case _:
|
|
91
|
+
raise ParseError('Unrecognized primary value: {self.value[0]}')
|
|
92
|
+
|
|
93
|
+
class _Identifier(peg):
|
|
94
|
+
# Identifier <- IdentStart IdentCont* Spacing
|
|
95
|
+
def preprocessing(self):
|
|
96
|
+
self.value = [''.join([v if isinstance(v,str) else v.evaluate() for v in self.value if not isinstance(v,_Spacing)])]
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
def evaluate(self):
|
|
100
|
+
identifier = ''.join([v if isinstance(v,str) else v.evaluate() for v in self.value if not isinstance(v,_Spacing)])
|
|
101
|
+
return ('_rules',identifier)
|
|
102
|
+
|
|
103
|
+
class _IdentStart(peg):
|
|
104
|
+
# IdentStart <- [a-zA-Z_]
|
|
105
|
+
def evaluate(self):
|
|
106
|
+
return reduce(lambda x,y: x + y if isinstance(y,str) else x + y.evaluate(),self.value,'')
|
|
107
|
+
|
|
108
|
+
class _IdentCont(peg):
|
|
109
|
+
# IdentCont <- IdentStart / [0-9]
|
|
110
|
+
def evaluate(self):
|
|
111
|
+
return reduce(lambda x,y: x + y if isinstance(y,str) else x + y.evaluate(),self.value,'')
|
|
112
|
+
|
|
113
|
+
class _Literal(peg):
|
|
114
|
+
# Literal <- ['] (!['] Char)* ['] Spacing / ["] (!["] Char)* ["] Spacing
|
|
115
|
+
def preprocessing(self):
|
|
116
|
+
self.value = [''.join([v if isinstance(v,str) else v.evaluate() for v in self.value if not isinstance(v,_Spacing)])]
|
|
117
|
+
return self
|
|
118
|
+
|
|
119
|
+
def evaluate(self):
|
|
120
|
+
return ('_literal_string',reduce(lambda x,y: x + y if isinstance(y,str) else x if isinstance(y,_Spacing) else x + y.evaluate(),self.value,''))
|
|
121
|
+
|
|
122
|
+
class _Class(peg):
|
|
123
|
+
# Class <- '[' (!']' Range)* ']' Spacing
|
|
124
|
+
def preprocessing(self):
|
|
125
|
+
self.value = [''.join([v if isinstance(v,str) else v.evaluate() for v in self.value if not isinstance(v,_Spacing)])]
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def evaluate(self):
|
|
129
|
+
return ('_character_class',reduce(lambda x,y: x + y if isinstance(y,str) else x if isinstance(y,_Spacing) else x + y.evaluate(),self.value,''))
|
|
130
|
+
|
|
131
|
+
class _Range(peg):
|
|
132
|
+
# Range <- Char '-' Char / Char
|
|
133
|
+
def evaluate(self):
|
|
134
|
+
return reduce(lambda x,y: x + y if isinstance(y,str) else x if isinstance(y,_Spacing) else x + y.evaluate(),self.value,'')
|
|
135
|
+
|
|
136
|
+
class _Char(peg):
|
|
137
|
+
# Char <- '\\' [nrt'"\[\]\\] / '\\' [0-2][0-7][0-7] / '\\' [0-7][0-7]? / !'\\' .
|
|
138
|
+
def evaluate(self):
|
|
139
|
+
return self.value[0]
|
|
140
|
+
|
|
141
|
+
class _LEFTARROW(peg):
|
|
142
|
+
# LEFTARROW <- '<-' Spacing
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
class _SLASH(peg):
|
|
146
|
+
# SLASH <- '/' Spacing
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
class _AND(peg):
|
|
150
|
+
# AND <- '&' Spacing
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
class _NOT(peg):
|
|
154
|
+
# NOT <- '!' Spacing
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
class _QUESTION(peg):
|
|
158
|
+
# QUESTION <- '?' Spacing
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
class _STAR(peg):
|
|
162
|
+
# STAR <- '*' Spacing
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
class _PLUS(peg):
|
|
166
|
+
# PLUS <- '+' Spacing
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
class _OPEN(peg):
|
|
170
|
+
# OPEN <- '(' Spacing
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
class _CLOSE(peg):
|
|
174
|
+
# CLOSE <- ')' Spacing
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
class _DOT(peg):
|
|
178
|
+
# DOT <- '.' Spacing
|
|
179
|
+
def evaluate(self):
|
|
180
|
+
return ('_any_character',self.value[0])
|
|
181
|
+
|
|
182
|
+
class _Spacing(peg):
|
|
183
|
+
# Spacing <- (Space / Comment)*
|
|
184
|
+
def evaluate(self):
|
|
185
|
+
return reduce(lambda x,y: x + y if isinstance(y,str) else x + y.evaluate(),self.value,'')
|
|
186
|
+
|
|
187
|
+
class _Comment(peg):
|
|
188
|
+
# Comment <- '#' (!EndOfLine .)* EndOfLine
|
|
189
|
+
def evaluate(self):
|
|
190
|
+
return reduce(lambda x,y: x + y if isinstance(y,str) else x + y.evaluate(),self.value,'')
|
|
191
|
+
|
|
192
|
+
class _Space(peg):
|
|
193
|
+
# Space <- ' ' / '\t' / EndOfLine
|
|
194
|
+
def evaluate(self):
|
|
195
|
+
return reduce(lambda x,y: x + y if isinstance(y,str) else x + y.evaluate(),self.value,'')
|
|
196
|
+
|
|
197
|
+
class _EndOfLine(peg):
|
|
198
|
+
# EndOfLine <- '\r\n' / '\n' / '\r'
|
|
199
|
+
def evaluate(self):
|
|
200
|
+
return reduce(lambda x,y: x + y if isinstance(y,str) else x + y.evaluate(),self.value,'')
|
|
201
|
+
|
|
202
|
+
class _EndOfFile(peg):
|
|
203
|
+
# EndOfFile <- !.
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
#================================================================================
|
|
207
|
+
# Persing Expression Grammar R and eS
|
|
208
|
+
#================================================================================
|
|
209
|
+
peg_R = {
|
|
210
|
+
'Grammar': ('_definition', 'Grammar', ('_sequence', ('_rules', 'Spacing'), ('_one_or_more', ('_rules', 'Definition')), ('_rules', 'EndOfFile'))),
|
|
211
|
+
'Definition': ('_definition', 'Definition', ('_sequence', ('_rules', 'Identifier'), ('_rules', 'LEFTARROW'), ('_rules', 'Expression'))),
|
|
212
|
+
'Expression': ('_definition', 'Expression', ('_sequence', ('_rules', 'Sequence'), ('_zero_or_more', ('_sequence', ('_rules', 'SLASH'), ('_rules', 'Sequence'))))),
|
|
213
|
+
'Sequence': ('_definition', 'Sequence', ('_one_or_more', ('_rules', 'Prefix'))),
|
|
214
|
+
'Prefix': ('_definition', 'Prefix', ('_sequence', ('_optional', ('_prioritized_choice', ('_rules', 'AND'), ('_rules', 'NOT'))), ('_rules', 'Suffix'))),
|
|
215
|
+
'Suffix': ('_definition', 'Suffix', ('_sequence', ('_rules', 'Primary'), ('_optional', ('_prioritized_choice', ('_rules', 'QUESTION'), ('_rules', 'STAR'), ('_rules', 'PLUS'))))),
|
|
216
|
+
'Primary': ('_definition', 'Primary', ('_prioritized_choice', ('_sequence', ('_rules', 'Identifier'), ('_not_predicate', ('_rules', 'LEFTARROW'))), ('_sequence', ('_rules', 'OPEN'), ('_rules', 'Expression'), ('_rules', 'CLOSE')), ('_rules', 'Literal'), ('_rules', 'Class'), ('_rules', 'DOT'))),
|
|
217
|
+
'Identifier': ('_definition', 'Identifier', ('_sequence', ('_rules', 'IdentStart'), ('_zero_or_more', ('_rules', 'IdentCont')), ('_rules', 'Spacing'))),
|
|
218
|
+
'IdentStart': ('_definition', 'IdentStart', ('_character_class', '[a-zA-Z_]')),
|
|
219
|
+
'IdentCont': ('_definition', 'IdentCont', ('_prioritized_choice', ('_rules', 'IdentStart'), ('_character_class', '[0-9]'))),
|
|
220
|
+
'Literal': ('_definition', 'Literal', ('_prioritized_choice', ('_sequence', ('_character_class', "[']"), ('_zero_or_more', ('_sequence', ('_not_predicate', ('_character_class', "[']")), ('_rules', 'Char'))), ('_character_class', "[']"), ('_rules', 'Spacing')), ('_sequence', ('_character_class', '["]'), ('_zero_or_more', ('_sequence', ('_not_predicate', ('_character_class', '["]')), ('_rules', 'Char'))), ('_character_class', '["]'), ('_rules', 'Spacing')))),
|
|
221
|
+
'Class': ('_definition', 'Class', ('_sequence', ('_literal_string', "'['"), ('_zero_or_more', ('_sequence', ('_not_predicate', ('_literal_string', "']'")), ('_rules', 'Range'))), ('_literal_string', "']'"), ('_rules', 'Spacing'))),
|
|
222
|
+
'Range': ('_definition', 'Range', ('_prioritized_choice', ('_sequence', ('_rules', 'Char'), ('_literal_string', "'-'"), ('_rules', 'Char')), ('_rules', 'Char'))),
|
|
223
|
+
'Char': ('_definition', 'Char', ('_prioritized_choice', ('_sequence', ('_literal_string', "'\\\\'"), ('_character_class', '[nrt\'"\\[\\]\\\\]')), ('_sequence', ('_literal_string', "'\\\\'"), ('_character_class', '[0-2]'), ('_character_class', '[0-7]'), ('_character_class', '[0-7]')), ('_sequence', ('_literal_string', "'\\\\'"), ('_character_class', '[0-7]'), ('_optional', ('_character_class', '[0-7]'))), ('_sequence', ('_not_predicate', ('_literal_string', "'\\\\'")), ('_any_character', '.')))), 'LEFTARROW': ('_definition', 'LEFTARROW', ('_sequence', ('_literal_string', "'<-'"), ('_rules', 'Spacing'))),
|
|
224
|
+
'SLASH': ('_definition', 'SLASH', ('_sequence', ('_literal_string', "'/'"), ('_rules', 'Spacing'))),
|
|
225
|
+
'AND': ('_definition', 'AND', ('_sequence', ('_literal_string', "'&'"), ('_rules', 'Spacing'))),
|
|
226
|
+
'NOT': ('_definition', 'NOT', ('_sequence', ('_literal_string', "'!'"), ('_rules', 'Spacing'))),
|
|
227
|
+
'QUESTION': ('_definition', 'QUESTION', ('_sequence', ('_literal_string', "'?'"), ('_rules', 'Spacing'))),
|
|
228
|
+
'STAR': ('_definition', 'STAR', ('_sequence', ('_literal_string', "'*'"), ('_rules', 'Spacing'))),
|
|
229
|
+
'PLUS': ('_definition', 'PLUS', ('_sequence', ('_literal_string', "'+'"), ('_rules', 'Spacing'))),
|
|
230
|
+
'OPEN': ('_definition', 'OPEN', ('_sequence', ('_literal_string', "'('"), ('_rules', 'Spacing'))),
|
|
231
|
+
'CLOSE': ('_definition', 'CLOSE', ('_sequence', ('_literal_string', "')'"), ('_rules', 'Spacing'))),
|
|
232
|
+
'DOT': ('_definition', 'DOT', ('_sequence', ('_literal_string', "'.'"), ('_rules', 'Spacing'))),
|
|
233
|
+
'Spacing': ('_definition', 'Spacing', ('_zero_or_more', ('_prioritized_choice', ('_rules', 'Space'), ('_rules', 'Comment')))),
|
|
234
|
+
'Comment': ('_definition', 'Comment', ('_sequence', ('_literal_string', "'#'"), ('_zero_or_more', ('_sequence', ('_not_predicate', ('_rules', 'EndOfLine')), ('_any_character', '.'))), ('_rules', 'EndOfLine'))),
|
|
235
|
+
'Space': ('_definition', 'Space', ('_prioritized_choice', ('_literal_string', "' '"), ('_literal_string', "'\\t'"), ('_rules', 'EndOfLine'))),
|
|
236
|
+
'EndOfLine': ('_definition', 'EndOfLine', ('_prioritized_choice', ('_literal_string', "'\\r\\n'"), ('_literal_string', "'\\n'"), ('_literal_string', "'\\r'"))),
|
|
237
|
+
'EndOfFile': ('_definition', 'EndOfFile', ('_not_predicate', ('_any_character', '.')))
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# EOF
|
pegase/pegase_gen_R.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Copyright © 2025 Shinichiro Ikeda
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
#
|
|
8
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
#
|
|
10
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
11
|
+
|
|
12
|
+
# Description:
|
|
13
|
+
# 文法ファイルの構文解析を行い、構文解析式の集合Rを生成する
|
|
14
|
+
# 文法ファイルがPEG文法以外で記述されている場合、その文法を定義したPEG文法定義ファイルを-gパラメータを使って指定する
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# $ python pegase_gen_R.py [-g PEG文法定義ファイル] 文法ファイル > generated_R.py
|
|
18
|
+
#
|
|
19
|
+
# 使用例:
|
|
20
|
+
# $ python pegase_gen_R.py epeg.peg > epeg_R.py
|
|
21
|
+
# $ python pegase_gen_R.py sql1992.peg > sql1992_R.py
|
|
22
|
+
# $ python pegase_gen_R.py -g epeg.peg sql2016.epeg > sql2016_R.py
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
from pegase import G
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#================================================================================
|
|
30
|
+
if __name__ == '__main__':
|
|
31
|
+
|
|
32
|
+
# パラメータ取得
|
|
33
|
+
args = sys.argv[1:]
|
|
34
|
+
|
|
35
|
+
# パラメータ取得(grammar)
|
|
36
|
+
grammar = None
|
|
37
|
+
if '-g' in args:
|
|
38
|
+
_idx = [i for i,p in enumerate(args) if p == '-g'][0]
|
|
39
|
+
grammar = args[_idx+1]
|
|
40
|
+
args = [p for i,p in enumerate(args) if i != _idx and i != _idx + 1]
|
|
41
|
+
|
|
42
|
+
# パラメータ取得(verbose)
|
|
43
|
+
verbose = 0
|
|
44
|
+
if '-v' in args:
|
|
45
|
+
_idx = [i for i,p in enumerate(args) if p == '-v'][0]
|
|
46
|
+
verbose = int([args[_idx+1]][0])
|
|
47
|
+
args = [p for i,p in enumerate(args) if i != _idx and i != _idx + 1]
|
|
48
|
+
|
|
49
|
+
# パラメータ取得(pegfile)
|
|
50
|
+
pegfile = [p for p in args if p[0] != '-' and os.path.isfile(p)][0]
|
|
51
|
+
|
|
52
|
+
# 抽象構文木を取得
|
|
53
|
+
AST = G(R = None if grammar is None else G().parse(grammar).evaluate(),verbose=verbose).parse(pegfile)
|
|
54
|
+
|
|
55
|
+
# 構文解析式Rを生成
|
|
56
|
+
R = AST.evaluate()
|
|
57
|
+
print(f'{os.path.splitext(os.path.basename(pegfile))[0]}_R =','{')
|
|
58
|
+
for a,e in R.items():
|
|
59
|
+
print(f'\t\'{a}\': {e},')
|
|
60
|
+
print('}')
|
|
61
|
+
|
|
62
|
+
# EOF
|
pegase/primitive.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
#-*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Copyright © 2025 Shinichiro Ikeda
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
#
|
|
8
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
9
|
+
#
|
|
10
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from functools import reduce
|
|
14
|
+
from enum import IntEnum
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#================================================================================
|
|
18
|
+
# Exceptions
|
|
19
|
+
#================================================================================
|
|
20
|
+
|
|
21
|
+
# Base class for exceptions
|
|
22
|
+
class Error(Exception):
|
|
23
|
+
def __init__(self,*p):
|
|
24
|
+
# 例外発生時の例外名からパッケージ名を除く
|
|
25
|
+
# pegase.primitive.Error -> pegase.Error
|
|
26
|
+
self.__class__.__module__ = __package__
|
|
27
|
+
|
|
28
|
+
# DefinitionError(message [,left_recursion])
|
|
29
|
+
class DefinitionError(Error):
|
|
30
|
+
def __str__(self):
|
|
31
|
+
if self.args[0] == 'left recursion detected':
|
|
32
|
+
recursions = self.args[1]
|
|
33
|
+
for A,r in recursions.items():
|
|
34
|
+
self.add_note(f' {A}: {r}')
|
|
35
|
+
return self.args[0]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ParseError(message [,Stream])
|
|
39
|
+
class ParseError(Error):
|
|
40
|
+
def __str__(self):
|
|
41
|
+
if len(self.args) >= 2 and isinstance(self.args[1],Stream):
|
|
42
|
+
s = self.args[1]
|
|
43
|
+
start = s._stream[:max(s._stream[:s._hwm].rfind('\n'),0)].rfind('\n')+1
|
|
44
|
+
end = len(s._stream) if s._stream[s._hwm:].find('\n') < 0 else s._hwm+s._stream[s._hwm:].find('\n')
|
|
45
|
+
linepos = s._hwm - 1 - s._stream[:s._hwm].rfind('\n')
|
|
46
|
+
s_no = len(re.findall('\n',s._stream[:start]))+1
|
|
47
|
+
e_no = len(re.findall('\n',s._stream[:s._hwm]))+1
|
|
48
|
+
self.add_note(f'Syntax error around line {s_no if s_no == e_no else str(s_no)+" to "+str(e_no)}:')
|
|
49
|
+
self.add_note(f'{s._stream[start:end]}')
|
|
50
|
+
self.add_note(f'{" "*linepos}^')
|
|
51
|
+
return self.args[0]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
#================================================================================
|
|
55
|
+
# Verbose Level
|
|
56
|
+
#================================================================================
|
|
57
|
+
class Verbose(IntEnum):
|
|
58
|
+
SILENT = 0
|
|
59
|
+
PROGRESS = 1
|
|
60
|
+
MUMBLE = 2
|
|
61
|
+
DEBUG = 3
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
#================================================================================
|
|
65
|
+
# Constructing Operators
|
|
66
|
+
#================================================================================
|
|
67
|
+
|
|
68
|
+
#----------------------------------------
|
|
69
|
+
# ε Empty String
|
|
70
|
+
#----------------------------------------
|
|
71
|
+
def _empty_string():
|
|
72
|
+
def _(s):
|
|
73
|
+
#DEBUG
|
|
74
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
75
|
+
print(f' - _empty_string')
|
|
76
|
+
return ''
|
|
77
|
+
return _
|
|
78
|
+
|
|
79
|
+
#----------------------------------------
|
|
80
|
+
# ' ' Literal String
|
|
81
|
+
# " " Literal String
|
|
82
|
+
#----------------------------------------
|
|
83
|
+
def _literal_string(pattern):
|
|
84
|
+
assert len(pattern) > 2 and ((pattern[0] == "'" and pattern[-1] == "'") or (pattern[0] == '"' and pattern[-1] == '"')), '_literal_string pattern must be enclosed in quotes or double quotes:'+str(pattern)
|
|
85
|
+
pattern = pattern.replace('\\n','\n').replace('\\r','\r').replace('\\t','\t').replace("\\'","'").replace('\\"','"').replace('\\[','[').replace('\\]',']').replace('\\\\','\\')
|
|
86
|
+
def _(s):
|
|
87
|
+
result = None
|
|
88
|
+
if s.stream()[:len(pattern[1:-1])] == pattern[1:-1]:
|
|
89
|
+
result = s.stream()[:len(pattern[1:-1])]
|
|
90
|
+
s.forward(len(pattern[1:-1]))
|
|
91
|
+
#DEBUG
|
|
92
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
93
|
+
print(f' * _literal_string : stream:{repr(result)} length:{len(pattern[1:-1])} pattern:{repr(pattern)}')
|
|
94
|
+
else:
|
|
95
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
96
|
+
print(f' _literal_string : stream:{repr(s.stream()[:10])} length:{len(pattern[1:-1])} pattern:{repr(pattern)}')
|
|
97
|
+
return result
|
|
98
|
+
return _
|
|
99
|
+
|
|
100
|
+
#----------------------------------------
|
|
101
|
+
# [ ] Character Class
|
|
102
|
+
#----------------------------------------
|
|
103
|
+
def _character_class(pattern):
|
|
104
|
+
assert len(pattern) > 2 and pattern[0] == '[' and pattern[-1] == ']', '_character_class pattern should be enclosed in "[" and "]":'+str(pattern)
|
|
105
|
+
p = re.compile(pattern)
|
|
106
|
+
def _(s):
|
|
107
|
+
result = None
|
|
108
|
+
m = p.match(s.stream())
|
|
109
|
+
if m:
|
|
110
|
+
s.forward(m.end())
|
|
111
|
+
result = [m.group()]
|
|
112
|
+
#DEBUG
|
|
113
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
114
|
+
print(f' * _characger_class : stream:{repr(result)} length:{len(pattern)} pattern:{repr(pattern)}')
|
|
115
|
+
else:
|
|
116
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
117
|
+
print(f' _character_class : stream:{repr(s.stream()[:10])} length:{len(pattern)} pattern:{repr(pattern)}')
|
|
118
|
+
return result
|
|
119
|
+
return _
|
|
120
|
+
|
|
121
|
+
#----------------------------------------
|
|
122
|
+
# . Any Character
|
|
123
|
+
#----------------------------------------
|
|
124
|
+
def _any_character(pattern):
|
|
125
|
+
assert pattern == '.', '_any_character pattern should be ".":'+str(pattern)
|
|
126
|
+
def _(s):
|
|
127
|
+
result = None
|
|
128
|
+
if len(s.stream()) > 0:
|
|
129
|
+
result = [s.stream()[0]]
|
|
130
|
+
s.forward(1)
|
|
131
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
132
|
+
print(f' * _any_character : stream:{repr(result)} length:{len(pattern)} pattern:{repr(pattern)}')
|
|
133
|
+
else:
|
|
134
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
135
|
+
print(f' _any_character : stream:{repr(s.stream()[:10])} length:{len(pattern)} pattern:{repr(pattern)}')
|
|
136
|
+
return result
|
|
137
|
+
return _
|
|
138
|
+
|
|
139
|
+
#----------------------------------------
|
|
140
|
+
# e? Optional
|
|
141
|
+
#----------------------------------------
|
|
142
|
+
def _optional(e):
|
|
143
|
+
def _(s):
|
|
144
|
+
#DEBUG
|
|
145
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
146
|
+
print(f' - _optional')
|
|
147
|
+
x = e(s)
|
|
148
|
+
return x or []
|
|
149
|
+
return _
|
|
150
|
+
|
|
151
|
+
#----------------------------------------
|
|
152
|
+
# e* Zero-or-more
|
|
153
|
+
#----------------------------------------
|
|
154
|
+
def _zero_or_more(e):
|
|
155
|
+
def _(s):
|
|
156
|
+
#DEBUG
|
|
157
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
158
|
+
print(f' - _zero_or_more')
|
|
159
|
+
result = []
|
|
160
|
+
while True:
|
|
161
|
+
x = e(s)
|
|
162
|
+
if x is None:
|
|
163
|
+
break
|
|
164
|
+
else:
|
|
165
|
+
result += x
|
|
166
|
+
return result
|
|
167
|
+
return _
|
|
168
|
+
|
|
169
|
+
#----------------------------------------
|
|
170
|
+
# e+ One-or-more
|
|
171
|
+
#----------------------------------------
|
|
172
|
+
def _one_or_more(e):
|
|
173
|
+
def _(s):
|
|
174
|
+
#DEBUG
|
|
175
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
176
|
+
print(f' - _one_or_more')
|
|
177
|
+
result = []
|
|
178
|
+
while True:
|
|
179
|
+
x = e(s)
|
|
180
|
+
if x is None:
|
|
181
|
+
break
|
|
182
|
+
else:
|
|
183
|
+
result += x
|
|
184
|
+
return result if len(result) > 0 else None
|
|
185
|
+
return _
|
|
186
|
+
|
|
187
|
+
#----------------------------------------
|
|
188
|
+
# &e And-redicate
|
|
189
|
+
#----------------------------------------
|
|
190
|
+
def _and_predicate(e):
|
|
191
|
+
def _(s):
|
|
192
|
+
#DEBUG
|
|
193
|
+
i = s.cursor()
|
|
194
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
195
|
+
print(f'& _and_predicate : at {i}')
|
|
196
|
+
x = e(s)
|
|
197
|
+
if x is None:
|
|
198
|
+
result = None
|
|
199
|
+
else:
|
|
200
|
+
result = []
|
|
201
|
+
s.backtracking(i)
|
|
202
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
203
|
+
print(f'<& _and_predicate : result = {result}, backtracing to {i} stream:{repr(s._stream[i:i+10])}')
|
|
204
|
+
return result
|
|
205
|
+
return _
|
|
206
|
+
|
|
207
|
+
#----------------------------------------
|
|
208
|
+
# !e Not-predicate
|
|
209
|
+
#----------------------------------------
|
|
210
|
+
def _not_predicate(e):
|
|
211
|
+
def _(s):
|
|
212
|
+
#DEBUG
|
|
213
|
+
i = s.cursor()
|
|
214
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
215
|
+
print(f'! _not_predicate : at {i}')
|
|
216
|
+
x = e(s)
|
|
217
|
+
if x is None:
|
|
218
|
+
result = []
|
|
219
|
+
else:
|
|
220
|
+
result = None
|
|
221
|
+
s.backtracking(i)
|
|
222
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
223
|
+
print(f'<! _not_predicate : result = {result}, backtracing to {i} stream:{repr(s._stream[i:i+10])}')
|
|
224
|
+
return result
|
|
225
|
+
return _
|
|
226
|
+
|
|
227
|
+
#----------------------------------------
|
|
228
|
+
# e1 e2 Sequence
|
|
229
|
+
#----------------------------------------
|
|
230
|
+
def _sequence(*es):
|
|
231
|
+
#DEBUG
|
|
232
|
+
# print('*** _sequence *es:',*es)
|
|
233
|
+
def _(s):
|
|
234
|
+
#DEBUG
|
|
235
|
+
result = []
|
|
236
|
+
i = s.cursor()
|
|
237
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
238
|
+
print(f'. _sequence _ : at {i} ',['e'+str(i+1) for i,_ in enumerate(es)])
|
|
239
|
+
for e in es:
|
|
240
|
+
x = e(s)
|
|
241
|
+
if x is None:
|
|
242
|
+
result = None
|
|
243
|
+
s.backtracking(i)
|
|
244
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
245
|
+
print(f'< _sequence : backtracing to {i} stream:{repr(s._stream[i:i+10])}')
|
|
246
|
+
break
|
|
247
|
+
else:
|
|
248
|
+
result += x
|
|
249
|
+
# if s.G.verbose >= Verbose.MUMBLE:
|
|
250
|
+
# if result is None:
|
|
251
|
+
# print(f'< _sequence : backtracing to {i} stream:{repr(s._stream[i:i+10])}')
|
|
252
|
+
# elif len(result) == 0:
|
|
253
|
+
# print(f' _sequence result :', result)
|
|
254
|
+
# else:
|
|
255
|
+
# print(f' _sequence result :', reduce(lambda x,y: x[:-1] + [''.join([x[-1],y])] if isinstance(x[-1],str) and isinstance(y,str) else x + [y], result[1:], [result[0]]))
|
|
256
|
+
return result if result is None or len(result) == 0 else reduce(lambda x,y: x[:-1] + [''.join([x[-1],y])] if isinstance(x[-1],str) and isinstance(y,str) else x + [y], result[1:], [result[0]])
|
|
257
|
+
return _
|
|
258
|
+
|
|
259
|
+
#----------------------------------------
|
|
260
|
+
# e1 / e2 Prioritized Choice
|
|
261
|
+
#----------------------------------------
|
|
262
|
+
def _prioritized_choice(*es):
|
|
263
|
+
#DEBUG
|
|
264
|
+
# print('*** _prioritized_choice *es:',*es)
|
|
265
|
+
def _(s):
|
|
266
|
+
#DEBUG
|
|
267
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
268
|
+
print(f' - _prioritized_choice :',['e'+str(i+1) for i,_ in enumerate(es)])
|
|
269
|
+
result = None
|
|
270
|
+
i = s.cursor()
|
|
271
|
+
for e in es:
|
|
272
|
+
x = e(s)
|
|
273
|
+
if x is None:
|
|
274
|
+
pass
|
|
275
|
+
else:
|
|
276
|
+
result = x
|
|
277
|
+
break
|
|
278
|
+
return result
|
|
279
|
+
return _
|
|
280
|
+
|
|
281
|
+
#----------------------------------------
|
|
282
|
+
# A <- e Definition
|
|
283
|
+
#----------------------------------------
|
|
284
|
+
def _definition(A,e,ex):
|
|
285
|
+
#DEBUG
|
|
286
|
+
# print('*** _definition A <- e:',A,e)
|
|
287
|
+
_A = '_' + A
|
|
288
|
+
def _(s):
|
|
289
|
+
#DEBUG
|
|
290
|
+
if s.G.verbose >= Verbose.DEBUG:
|
|
291
|
+
print(f' _definition _ A <- e : {A} <- {ex}')
|
|
292
|
+
x = e(s)
|
|
293
|
+
if _A not in s.G.VN:
|
|
294
|
+
# VNにAが定義されていない場合、Aのクラス定義を生成してVNに登録する
|
|
295
|
+
s.G.VN[_A] = type(_A,s.G.parent,{})
|
|
296
|
+
#VERBOSE
|
|
297
|
+
if x is not None and s.G.verbose >= Verbose.MUMBLE:
|
|
298
|
+
print(f' ** _definition _ A <- e : {A} <- {x}')
|
|
299
|
+
return x if x is None else [s.G.VN[_A](x)]
|
|
300
|
+
return _
|
|
301
|
+
|
|
302
|
+
#----------------------------------------
|
|
303
|
+
# R(A) Rules
|
|
304
|
+
#----------------------------------------
|
|
305
|
+
def _rules(A):
|
|
306
|
+
# print('*** _rules A:',A)
|
|
307
|
+
def _(s):
|
|
308
|
+
#DEBUG
|
|
309
|
+
if s.G.verbose >= Verbose.MUMBLE:
|
|
310
|
+
print(f' - _rules _ A : {A}')
|
|
311
|
+
return s.G.R[A](s)
|
|
312
|
+
return _
|
|
313
|
+
|
|
314
|
+
#================================================================================
|
|
315
|
+
# "readable rules" to expressions conversion
|
|
316
|
+
#================================================================================
|
|
317
|
+
def _expression(expr):
|
|
318
|
+
f,*p = expr
|
|
319
|
+
match f:
|
|
320
|
+
case '_empty_string':
|
|
321
|
+
x = _empty_string()
|
|
322
|
+
case '_literal_string':
|
|
323
|
+
x = _literal_string(*p)
|
|
324
|
+
case '_character_class':
|
|
325
|
+
x = _character_class(*p)
|
|
326
|
+
case '_any_character':
|
|
327
|
+
x = _any_character(*p)
|
|
328
|
+
case '_optional':
|
|
329
|
+
x = _optional(_expression(*p))
|
|
330
|
+
case '_zero_or_more':
|
|
331
|
+
x = _zero_or_more(_expression(*p))
|
|
332
|
+
case '_one_or_more':
|
|
333
|
+
x = _one_or_more(_expression(*p))
|
|
334
|
+
case '_and_predicate':
|
|
335
|
+
x = _and_predicate(_expression(*p))
|
|
336
|
+
case '_not_predicate':
|
|
337
|
+
x = _not_predicate(_expression(*p))
|
|
338
|
+
case '_sequence':
|
|
339
|
+
x = _sequence(*[_expression(e) for e in p])
|
|
340
|
+
case '_prioritized_choice':
|
|
341
|
+
x = _prioritized_choice(*[_expression(e) for e in p])
|
|
342
|
+
case '_definition':
|
|
343
|
+
A,e = p
|
|
344
|
+
x = _definition(A,_expression(e),e)
|
|
345
|
+
case '_rules':
|
|
346
|
+
x = _rules(*p)
|
|
347
|
+
case _:
|
|
348
|
+
raise DefinitionError(f'Unrecognized primitive: {f}')
|
|
349
|
+
return x
|
|
350
|
+
|
|
351
|
+
#================================================================================
|
|
352
|
+
# Stream Class
|
|
353
|
+
#================================================================================
|
|
354
|
+
class Stream:
|
|
355
|
+
def __init__(self,stream,G):
|
|
356
|
+
self._stream = stream
|
|
357
|
+
self._cursor = 0
|
|
358
|
+
self._hwm = 0
|
|
359
|
+
self.G = G
|
|
360
|
+
|
|
361
|
+
def __repr__(self):
|
|
362
|
+
return "{1}.{0.__class__.__name__}({0._stream})".format(self,__package__)
|
|
363
|
+
|
|
364
|
+
def stream(self):
|
|
365
|
+
return self._stream[self._cursor:]
|
|
366
|
+
|
|
367
|
+
def cursor(self):
|
|
368
|
+
return self._cursor
|
|
369
|
+
|
|
370
|
+
def forward(self,i):
|
|
371
|
+
self._cursor += i
|
|
372
|
+
self._hwm = max(self._cursor,self._hwm)
|
|
373
|
+
|
|
374
|
+
def backtracking(self,i):
|
|
375
|
+
self._cursor = i
|
|
376
|
+
|
|
377
|
+
# EOF
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pegase
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Parsing Expression Grammar Abstract Syntax Extension
|
|
5
|
+
Author: Shinichiro Ikeda
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# Pegase
|
|
11
|
+
Parsing Expression Grammar Abstract Syntax Extension
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Overview
|
|
15
|
+
Pegase is a parsing module implemented in Python based on "Parsing Expression Grammars: A Recognition-Based Syntactic Foundation," a paper presented by Bryan Ford of the Massachusetts Institute of Technology at POPL 2004 (ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages).
|
|
16
|
+
The PEG paper is available at https://bford.info/pub/lang/peg/.
|
|
17
|
+
|
|
18
|
+
The Pegase grammar object (G) defines a grammar based on externally provided syntax rules and non-terminal objects, and provides functionality to perform parsing via its `parse` method.
|
|
19
|
+
The parsing result is constructed and output as a syntax tree composed of non-terminal objects.
|
|
20
|
+
|
|
21
|
+
When parsing syntax definitions written in PEG, you can use the PEG-compliant syntax rules and non-terminal objects built into Pegase to execute the parse and obtain the resulting syntax rules.
|
|
22
|
+
|
|
23
|
+
# Usage
|
|
24
|
+
A grammar object in Pegase is created by instantiating the `G` class from the `pegase` package. When creating the grammar object, you specify the following parameters: the package name (VN) defining the non-terminal symbol objects, the S-expression (R) defining the syntax rules, and the name (eS) of the S-expression serving as the starting point for parsing. If these parameters are omitted, the internal VN, R, and eS values for PEG grammar parsing within the `pegase` package are used.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
from pegase import G
|
|
28
|
+
PEG = G()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
By parsing the grammar definition of a programming language (written in PEG syntax) using this PEG grammar object (PEG), you can obtain an abstract syntax tree; evaluating this tree allows you to derive the syntax rules (R) for the programming language.
|
|
32
|
+
|
|
33
|
+
As an example, we will define a language capable of performing the four basic arithmetic operations and save it as `basic-calc.peg`.
|
|
34
|
+
|
|
35
|
+
```basic-calc.peg
|
|
36
|
+
expr <- term (('+' / '-') term)*
|
|
37
|
+
term <- factor (('*' / '/') factor)*
|
|
38
|
+
factor <- [0-9]+ / '(' expr ')'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
We parse this grammar definition file (basic-calc.peg) using the `parse` method of the previously created PEG grammar object, and then evaluate the resulting abstract syntax tree (AST) to obtain the syntax rules (R) of basic-calc.peg.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
basic_ST = PEG.parse('basic-calc.peg')
|
|
45
|
+
basic_R = basic_ST.evaluate()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The evaluation logic for each node of the abstract syntax tree is implemented as a Python package, where the names of the PEG grammar's non-terminal symbols serve as class names. In the arithmetic operation example presented here, we define classes named `expr`, `term`, and `factor` to represent the non-terminal symbol objects.
|
|
49
|
+
|
|
50
|
+
Create a Python package named `basic-calc_VN.py` containing non-terminal symbol objects for a language that supports the four basic arithmetic operations.
|
|
51
|
+
|
|
52
|
+
```python:basic-calc_VN.py
|
|
53
|
+
from functools import reduce
|
|
54
|
+
from pegase import A
|
|
55
|
+
|
|
56
|
+
def calc(x,opcode,y):
|
|
57
|
+
match opcode:
|
|
58
|
+
case '+':
|
|
59
|
+
return x + y
|
|
60
|
+
case '-':
|
|
61
|
+
return x- y
|
|
62
|
+
case '*':
|
|
63
|
+
return x * y
|
|
64
|
+
case '/':
|
|
65
|
+
return x / y
|
|
66
|
+
|
|
67
|
+
class _expr(A):
|
|
68
|
+
def evaluate(self):
|
|
69
|
+
return reduce(lambda x,y:calc(x,y[0],y[1].evaluate()),[('+',self.value[0])] if len(self.value) == 1 else [('+',self.value[0])] + list(zip(self.value[1::2],self.value[2::2])),0)
|
|
70
|
+
|
|
71
|
+
class _term(A):
|
|
72
|
+
def evaluate(self):
|
|
73
|
+
return reduce(lambda x,y:calc(x,y[0],y[1].evaluate()),[('+',self.value[0])] if len(self.value) == 1 else [('+',self.value[0])] + list(zip(self.value[1::2],self.value[2::2])),0)
|
|
74
|
+
|
|
75
|
+
class _factor(A):
|
|
76
|
+
def evaluate(self):
|
|
77
|
+
match self.value[0]:
|
|
78
|
+
case '(':
|
|
79
|
+
return self.value[1].evaluate()
|
|
80
|
+
case _:
|
|
81
|
+
return int(''.join(self.value))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A grammar object for a language supporting the four basic arithmetic operations is generated based on the non-terminal symbol object name, syntax rules, and the start symbol name; the result of the arithmetic operation is then obtained by parsing a mathematical expression using the grammar object's `parse` method and evaluating the result.
|
|
85
|
+
|
|
86
|
+
Create the main program for the four basic arithmetic operations as `basic-calc.py`.
|
|
87
|
+
|
|
88
|
+
```python:basic-calc.py
|
|
89
|
+
import sys
|
|
90
|
+
from pegase import G
|
|
91
|
+
|
|
92
|
+
if __name__ == '__main__':
|
|
93
|
+
PEG = G()
|
|
94
|
+
basic_ST = PEG.parse('basic-calc.peg')
|
|
95
|
+
basic_R = basic_ST.evaluate()
|
|
96
|
+
basic_G = G('basic-calc_VN',basic_R,'expr')
|
|
97
|
+
result = basic_G.parse(sys.argv[1]).evaluate()
|
|
98
|
+
print(result)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
The execution results are as follows.
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
$ python basic-calc.py '1+2*3/(4-5)'
|
|
106
|
+
-5.0
|
|
107
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pegase/LICENSE,sha256=GMEwjerQru8MF-_DbBCbU9cvQyiirI2PDCIcQh4ejyk,1067
|
|
2
|
+
pegase/LICENSE.jp,sha256=DiIT58eb7DLyPfc1GUTeCwqfpc4ICeLk_w-vXfpUHpE,1423
|
|
3
|
+
pegase/README.md,sha256=agwOM7kyj6ZEeVI1GP4jNIp2f6SuHToHLPUb_7pf6xU,5446
|
|
4
|
+
pegase/__init__.py,sha256=Nquww0UHn_v7I-5n6wN9IK9hjEfhlyQDyl1qJq1b7z0,209
|
|
5
|
+
pegase/flatten.py,sha256=ZwdK5beqwcVtT5-AkureljeB0sbBsqrGidmDVXKy-5s,1782
|
|
6
|
+
pegase/grammar.py,sha256=Gd8hjDS2ihdK37mY9voMkV_DGqaANmSWUcoimwvgjnQ,7539
|
|
7
|
+
pegase/nonterminal.py,sha256=AUQZkLvJGr2PEFOVjvygYSGg2Vt0_bYgkWa4SuB44no,2358
|
|
8
|
+
pegase/peg.peg,sha256=nYjMzQ-QPtMJunP95V9dqx6Uf2jIGgYEFNHb-o51zFk,1027
|
|
9
|
+
pegase/peg.py,sha256=HoaPhtdxRtERsbkHtFfCD4hTKjRb4A7EWo9OfKX_Yzg,11819
|
|
10
|
+
pegase/pegase_gen_R.py,sha256=w9wD8IGkgT3INo0500nLQbpIpBfAlGFgcJUnqpJ8z_8,2777
|
|
11
|
+
pegase/primitive.py,sha256=7IKPLVCwYVuvcdEewqUj-NUgCrQWgZyS3DQLTgwOt-E,11940
|
|
12
|
+
pegase-1.0.0.dist-info/METADATA,sha256=m-QEx2GU_oIaiPzmt0fv8ycmnxhJFIPaKbzeThbR-y8,4668
|
|
13
|
+
pegase-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
14
|
+
pegase-1.0.0.dist-info/top_level.txt,sha256=dtrPD-lRnVSj8wFyh1Kr0vQtWt1y_MdEy1T-7ZLfkvo,7
|
|
15
|
+
pegase-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pegase
|