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 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
@@ -0,0 +1,6 @@
1
+ from .grammar import G
2
+ from .nonterminal import A
3
+ from .primitive import Verbose,DefinitionError,ParseError
4
+ from .flatten import flatten
5
+
6
+ __all__ = ['G','A','Verbose','DefinitionError','ParseError','flatten']
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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ pegase