これを書いている現在(2023/1/25),私が現在進行形でエラーの分類をして書き換えているので見ているエラーの箇所は変わっている可能性が高いのでそこは注意してください
(1)の方がパーサーの幹の部分の説明をしているのでもし読んでいなければそちらを読むことをお勧めします
はじめに
一応これでParserを一通り軽くではあるが解説をしたことになる
(1)の最後に追記していく形で修正事項やおすすめPRを書こうかなと思っている
今回説明するのは以下の通り
- try_reduce_block
- try_reduce_decoretor(s)
- try_reduce_type_app_args
- try_reduce_elem(s)
- try_reduce_array
- opt_reduce_args
- try_reduce_default_parameters
- try_reduce_unary
- try_reduce_brace_container
- try_reduce_record
- try_reduce_normal_dict
- try_reduce_set
- try_reduce_nonempty_tuple
- try_reduce_lit
- try_reduce_string_interpolation
- try_reduce_stream_operator
- collect_last_binop_on_stack
try_reduce_block
=
, :
, ->
, =>
の記号が来たら呼び出される
一応Class属性の定義(ClassName.
)にもブロックが必要だが,それには使われていない
ブロックの判定にはIndent
が使われる
そしてIndent
が呼ばれたら必ずDedent
が対応して呼ばれるようになっている
あとは,ブロックの最後は必ず返り値になるため,ブロックの最後に定義を持ってくることはできない
# これはできない
a =
b = 1
# これなら良い
a =>
b = 2
None
fn try_reduce_block(&mut self) -> ParseResult<Block> {
debug_call_info!(self);
let mut block = Block::with_capacity(2);
// 一行の時
if !self.cur_is(Newline) {
// 一行分をパースする
let chunk = self
.try_reduce_chunk(true, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
block.push(chunk);
if !self.cur_is(Dedent) && !self.cur_category_is(TC::Separator) {
let err = self
.skip_and_throw_invalid_chunk_err(caused_by!(), block.last().unwrap().loc());
debug_exit_info!(self);
self.errs.push(err);
}
// ブロックの呼び出しで,最後を定義にすることはできない
if block.last().unwrap().is_definition() {
let err = ParseError::simple_syntax_error(0, block.last().unwrap().loc());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
} else {
debug_exit_info!(self);
return Ok(block);
}
}
// 上記の後、もしくはmultiline_blockでtrueで呼び出されたときにここで改行が処理される
if !self.cur_is(Newline) {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
// 改行だけならスキップ
while self.cur_is(Newline) {
self.skip();
}
// 2行目以降なら必ずインデントが無いとブロックの塊になっていない
if !self.cur_is(Indent) {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
self.skip(); // Indent
// 2行目以降のパースをする
loop {
match self.peek_kind() {
// 空行は`Dedent`をスキップする
Some(Newline) if self.nth_is(1, Dedent) => {
let nl = self.lpop();
self.skip();
self.restore(nl);
break;
}
// `Dedent`はブロックの最後なので解析終了
Some(Dedent) => {
self.skip();
break;
}
// 改行やセミコロンは一つのchunkの区切りになるのでchunkでパースされたら
// 次のループでこれが呼ばれる可能性が高い
Some(Newline | Semi) => {
self.skip();
}
// ブロックの最後でこれが来ても終了
Some(EOF) => {
break;
}
// とりあえずchunkでパースし,それをブロックに挿入する
Some(_) => {
if let Ok(expr) = self.try_reduce_chunk(true, false) {
block.push(expr);
// chunkの後ろに更にchunkがあればエラー
if !self.cur_is(Dedent) && !self.cur_category_is(TC::Separator) {
let err = self.skip_and_throw_invalid_chunk_err(
caused_by!(),
block.last().unwrap().loc(),
);
debug_exit_info!(self);
self.errs.push(err);
}
}
}
_ => switch_unreachable!(),
}
}
// 複数行をパースしているはずなので,ブロックが空になる場合はパーサーのバグである可能性が高い
if block.is_empty() {
let loc = if let Some(u) = self.peek() {
u.loc()
} else {
Location::Unknown
};
let err = ParseError::failed_to_analyze_block(line!() as usize, loc);
self.errs.push(err);
debug_exit_info!(self);
Err(())
// 一行の時と同様に,最後が定義なのはエラーになる
} else if block.last().unwrap().is_definition() {
let err = ParseError::invalid_chunk_error(line!() as usize, block.loc());
self.errs.push(err);
debug_exit_info!(self);
Err(())
} else {
debug_exit_info!(self);
Ok(block)
}
}
try_reduce_decoretor(s)
デコレータを定義するためのメソッド
単純に@
の後ろの式を見ている
fn opt_reduce_decorator(&mut self) -> ParseResult<Option<Decorator>> {
debug_call_info!(self);
if self.cur_is(TokenKind::AtSign) {
self.lpop(); // @をスキップ
// デコレータは式でそれを再度デコレータに変換して返す
let expr = self
.try_reduce_expr(false, false, false, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(Some(Decorator::new(expr)))
} else {
debug_exit_info!(self);
Ok(None)
}
}
上のデコレータを@
が来なくなるまで呼び出している
fn opt_reduce_decorators(&mut self) -> ParseResult<HashSet<Decorator>> {
debug_call_info!(self);
// 引数などと違い重複した宣言は自動で削除される
let mut decs = set![];
while let Some(deco) = self
.opt_reduce_decorator()
.map_err(|_| self.stack_dec(fn_name!()))?
{
decs.insert(deco);
if self.cur_is(Newline) {
self.skip();
} else {
// デコレータのある行に余計な構文があればエラー
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
debug_exit_info!(self);
Ok(decs)
}
try_reduce_type_app_args
多相関数の引数のパースだと思う
以下の|~|
のところだと思われる
add1|T : Int or Str| x: T = x + 1
fn try_reduce_type_app_args(&mut self) -> ParseResult<TypeAppArgs> {
debug_call_info!(self);
assert!(self.cur_is(VBar));
let l_vbar = self.lpop(); // 左のバーティカルバー
// 多相関数の場合は必ず引数が与えられるので`args`を使用する
let args = self
.try_reduce_args(true)
.map_err(|_| self.stack_dec(fn_name!()))?;
if self.cur_is(VBar) { // 右のバーティカルバー
let r_vbar = self.lpop();
debug_exit_info!(self);
Ok(TypeAppArgs::new(l_vbar, args, r_vbar))
} else {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
Err(())
}
}
try_reduce_elem
try_reduce_elems
から呼ばれるメソッド
配列の連続要素の一つをパースするのに使われている
配列にはキーワード引数を取ることができないのでパースされたのは必ずポジショナル引数として返される
fn try_reduce_elem(&mut self) -> ParseResult<PosArg> {
debug_call_info!(self);
match self.peek() {
Some(_) => {
let expr = self
.try_reduce_expr(false, false, false, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(PosArg::new(expr))
}
None => switch_unreachable!(),
}
}
try_reduce_elemes
try_reduce_elemes
では配列の種類を判断してパースをしている
配列の種類は4つある
- 一般的な連続要素: [1, 2, 3, 4]
- 型指定: [Int; 10] # 型の宣言
- 長さ指定: [1; 10] # 要素の宣言
- 内包表記(未実装): [i*2 | i <- [0, 1, 2, 3]]
型指定と長さ指定は処理が同じ
今はパースはされるが,バグがあり実行ができない
pub enum ArrayInner {
Normal(Args),
WithLength(PosArg, Expr),
Comprehension {
elem: PosArg,
generators: Vec<(Identifier, Expr)>,
guards: Vec<Expr>,
},
}
タプルでも使われていたが,今は使われていない
なので,中の処理にタプルの処理が混じっているが,使われないはず
fn try_reduce_elems(&mut self) -> ParseResult<ArrayInner> {
debug_call_info!(self);
// ], }, )かDedentが来たら空の配列を返す
// 現状は配列でしか使われていないのでこれはRSqBrに変えても良いかもしれない
if self.cur_category_is(TC::REnclosure) {
let args = Args::new(vec![], vec![], None);
debug_exit_info!(self);
return Ok(ArrayInner::Normal(args));
}
// ここで一つ目の要素をパースして次の要素で配列の種類を判定する
let first = self
.try_reduce_elem()
.map_err(|_| self.stack_dec(fn_name!()))?;
let mut elems = Args::new(vec![first], vec![], None);
match self.peek_kind() {
// 型と長さが指定されたタイプ
Some(Semi) => {
self.lpop();
let len = self
.try_reduce_expr(false, false, false, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
// 長さ指定と型指定はここで返される
// 型の指定もこれで返して良いのかはわからない
return Ok(ArrayInner::WithLength(elems.remove_pos(0), len));
}
// 内包表記なので未実装
Some(VBar) => {
let err = ParseError::feature_error(
line!() as usize,
self.peek().unwrap().loc(),
"comprehension",
);
self.lpop();
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
// タプルを処理していた名残で,RSqBrがマッチングする
Some(RParen | RSqBr | RBrace | Dedent | Comma) => {}
// 上記以外は単純に要素になるのでそれを処理する
Some(_) => {
let elem = self
.try_reduce_elem()
.map_err(|_| self.stack_dec(fn_name!()))?;
elems.push_pos(elem);
}
// これが来たらパーサーのバグ
None => {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
// "[elem1, elem2"までパース出来ているはずなので,
// ここまで来れるのは普通の配列のみ
loop {
match self.peek_kind() {
// 一般的な配列の要素
Some(Comma) => {
self.skip();
// [1, 2,,みたいな時
if self.cur_is(Comma) {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
elems.push_pos(
self.try_reduce_elem()
.map_err(|_| self.stack_dec(fn_name!()))?,
);
}
// これも恐らく上記と同じでTupleなどの名残
Some(RParen | RSqBr | RBrace | Dedent) => {
break;
}
_ => {
self.skip();
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
}
debug_exit_info!(self);
Ok(ArrayInner::Normal(elems))
}
try_reduce_array
配列のパースをするためのメソッド
上記のelems(elem)
をここから呼び出して配列のパースをしている
elems
からenumで返されるのでそれをマッチングして式に変換することをしている
fn try_reduce_array(&mut self) -> ParseResult<Array> {
debug_call_info!(self);
let l_sqbr = self.lpop();
// ここでもう中身のパースは終わり
let inner = self
.try_reduce_elems()
.map_err(|_| self.stack_dec(fn_name!()))?;
let r_sqbr = self.lpop();
if !r_sqbr.is(RSqBr) {
self.errs
.push(ParseError::simple_syntax_error(0, r_sqbr.loc()));
debug_exit_info!(self);
return Err(());
}
// ここから先で3種類のいずれかに分類して返す
let arr = match inner {
// [1, 2, 3]の一般的な配列
ArrayInner::Normal(mut elems) => {
// 上記のelemsでTuple型で返されている
// Tuple自体をパースするのにもつかわれていたので,その名残でこの処理がると思われる
let elems = if elems
.pos_args()
.get(0)
.map(|pos| match &pos.expr {
// 丸括弧はないのでこれは必ずtrueのはず
Expr::Tuple(tup) => tup.paren().is_none(),
_ => false,
})
.unwrap_or(false)
{
enum_unwrap!(elems.remove_pos(0).expr, Expr::Tuple:(Tuple::Normal:(_))).elems
} else {
elems
};
Array::Normal(NormalArray::new(l_sqbr, r_sqbr, elems))
}
// [Int; length]の配列の型と長さを指定するタイプ
ArrayInner::WithLength(elem, len) => {
Array::WithLength(ArrayWithLength::new(l_sqbr, r_sqbr, elem, len))
}
// 配列の内包表記,まだ未実装
ArrayInner::Comprehension { .. } => {
self.errs.push(ParseError::feature_error(
line!() as usize,
Location::concat(&l_sqbr, &r_sqbr),
"array comprehension",
));
debug_exit_info!(self);
return Err(());
}
};
debug_exit_info!(self);
Ok(arr)
}
opt_reduce_args
引数がない場合があるのでOptionになっている引数のパース
関数の宣言,呼び出し,メソッドの宣言,メソッドの呼び出しでこれが呼ばれる
単純に引数をパースするtry_reduce_args
をOptionに包んでいるだけ
fn opt_reduce_args(&mut self, in_type_args: bool) -> Option<ParseResult<Args>> {
debug_call_info!(self);
match self.peek() {
Some(t)
if t.category_is(TC::Literal)
|| t.is(StrInterpLeft)
|| t.is(Symbol)
|| t.category_is(TC::UnaryOp)
|| t.is(LParen)
|| t.is(LSqBr)
|| t.is(LBrace)
|| t.is(UBar) =>
{
Some(self.try_reduce_args(in_type_args))
}
// 公開・非公開(明示的も)のクラス属性やデータパックの宣言をはじくため
Some(t)
if (t.is(Dot) || t.is(DblColon))
&& !self.nth_is(1, Newline)
&& !self.nth_is(1, LBrace) =>
{
Some(self.try_reduce_args(in_type_args))
}
_ => None,
}
}
try_reduce_default_parameters
chunk
やexpr
から呼ばれるので関数やメソッドが確定したときかつ,:=
があったときにこれが呼ばれる
パースの内容はキーワード引数のパースの時と同じで,識別子を判断しそれに代入する式を求めているだけ
f x:=1, y:Int:=2 = something
ClassName.
method(parm:=1, param:=[1, 2]) = something
fn try_reduce_default_parameters(
&mut self,
stack: &mut Vec<ExprOrOp>,
in_brace: bool,
) -> ParseResult<Tuple> {
debug_call_info!(self);
// とりあえず初めの要素(キーワード)はパースされているはずなのでそれを取り出す
let first_elem = enum_unwrap!(stack.pop(), Some:(ExprOrOp::Expr:(_)));
// 取り出された要素は必ず識別子(キーワード)でないといけない
let (keyword, t_spec) = match first_elem {
Expr::Accessor(Accessor::Ident(ident)) => (ident.name.into_token(), None),
// 型指定もできる
Expr::TypeAsc(tasc) => {
if let Expr::Accessor(Accessor::Ident(ident)) = *tasc.expr {
(
ident.name.into_token(),
Some(TypeSpecWithOp::new(tasc.op, tasc.t_spec)),
)
} else {
let err = ParseError::simple_syntax_error(line!() as usize, tasc.loc());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
other => {
let err = ParseError::simple_syntax_error(line!() as usize, other.loc());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
};
self.skip(); // :=
// 代入されるキーワードは既に上記でパースされているので代入する式があるはず
let rhs = self
.try_reduce_expr(false, false, in_brace, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
//
let first_elem = PosOrKwArg::Kw(KwArg::new(keyword, t_spec, rhs));
// 最初の要素がパース出来たので,(kw:=rhs|ここまで
// 空じゃないことが保証されている
let tuple = self
.try_reduce_nonempty_tuple(first_elem, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(tuple)
}
try_reduce_unary
単項演算子のパース
そもそも単項演算子が使えるのかが疑問になっている
fn try_reduce_unary(&mut self) -> ParseResult<UnaryOp> {
debug_call_info!(self);
let op = self.lpop();
let expr = self
.try_reduce_expr(false, false, false, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(UnaryOp::new(op, expr))
}
try_reduce_brace_container
{}
の中身をパースするためのメソッド
コレクションの内3種類をパースする
- レコード: {x = 1; y = 1}, {x; y}
- セット: {1, 2, 3}, {Int; 3}
- 辞書: {"a": 1, "b": 2}, {Str: Int}
最初の要素をパースして,その次が=
や;
,:
かどうかでそれぞれのコレクションのパースに移行している
fn try_reduce_brace_container(&mut self) -> ParseResult<BraceContainer> {
debug_call_info!(self);
assert!(self.cur_is(LBrace));
let l_brace = self.lpop();
if self.cur_is(Newline) {
self.skip();
if self.cur_is(Indent) {
self.skip();
} else {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
// 空のコレクション
if let Some(first) = self.peek() {
// {} 空のセット
if first.is(RBrace) {
let r_brace = self.lpop();
let arg = Args::empty();
let set = NormalSet::new(l_brace, r_brace, arg);
debug_exit_info!(self);
return Ok(BraceContainer::Set(Set::Normal(set)));
}
// {=} 空のレコード
if first.is(Equal) {
let _eq = self.lpop();
if let Some(t) = self.peek() {
if t.is(RBrace) {
let r_brace = self.lpop();
debug_exit_info!(self);
return Ok(BraceContainer::Record(Record::empty(l_brace, r_brace)));
}
}
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
// {:} 空の辞書
if first.is(Colon) {
let _colon = self.lpop();
if let Some(t) = self.peek() {
if t.is(RBrace) {
let r_brace = self.lpop();
let dict = NormalDict::new(l_brace, r_brace, vec![]);
debug_exit_info!(self);
return Ok(BraceContainer::Dict(Dict::Normal(dict)));
}
}
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
let first = self
.try_reduce_chunk(false, true)
.map_err(|_| self.stack_dec(fn_name!()))?;
match first {
// {x=の時はレコード型
Expr::Def(def) => {
let attr = RecordAttrOrIdent::Attr(def);
let record = self
.try_reduce_record(l_brace, attr)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(BraceContainer::Record(record))
}
// {x;の時はレコードのショートバージョン
// ただこれは{x;length}のセット型の長さ指定とパースが被る可能性がある
Expr::Accessor(acc)
if self.cur_is(Semi)
// 一応長さはNat型のみだが,Int型とか他の無効な長さが来たらパースされる
&& !self.nth_is(1, TokenKind::NatLit)
&& !self.nth_is(1, UBar) =>
{
let ident = match acc {
Accessor::Ident(ident) => ident,
other => {
let err = ParseError::simple_syntax_error(line!() as usize, other.loc());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
};
let attr = RecordAttrOrIdent::Ident(ident);
let record = self
.try_reduce_record(l_brace, attr)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(BraceContainer::Record(record))
}
// {x:が来たら辞書型
other if self.cur_is(Colon) => {
let dict = self
.try_reduce_normal_dict(l_brace, other)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(BraceContainer::Dict(Dict::Normal(dict)))
}
// 上記の以外は全て集合型としてパースされる
// {1,とか{Type;1とか
other => {
let set = self
.try_reduce_set(l_brace, other)
.map_err(|_| self.stack_dec(fn_name!()))?;
debug_exit_info!(self);
Ok(BraceContainer::Set(set))
}
}
}
try_reduce_record
レコード型をパースするためのメソッド
レコード型は3種類ある
{x = 1; y = 1} # デフォルト
{x; y} # ショート
{x; y = 1; z = 2} # ミックス
john = { # 上記のはいずれも改行でも書ける
name = "John"
age = 21
}
他のコレクションとは違い,セミコロンまたは改行で属性を分けるのでそこだけ注意が必要
余談だが,Erg言語ではファイル名をレコード名,ファイルの各関数などがこのレコード型の属性として処理されているらしい
なので上記のスクリプトなどは以下の様な感じで処理されているらしい
record = {
_ = {x = 1; y = 1}
...
john = {name="John";age=21}
}
三種類それぞれはEnumで分けられており,それぞれを
fn try_reduce_record(
&mut self,
l_brace: Token,
first_attr: RecordAttrOrIdent,
) -> ParseResult<Record> {
debug_call_info!(self);
let mut attrs = vec![first_attr];
loop {
match self.peek_kind() {
Some(Newline | Semi) => {
self.skip();
}
// 改行の時の終了条件
Some(Dedent) => {
self.skip(); // dedentがスキップ
// Dedentがスキップされたら必ず}がある
if self.cur_is(RBrace) {
let r_brace = self.lpop();
debug_exit_info!(self);
return Ok(Record::new_mixed(l_brace, r_brace, attrs));
} else {
// self.restore(other);
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
// 改行じゃないときの終了条件
Some(RBrace) => {
let r_brace = self.lpop();
debug_exit_info!(self);
return Ok(Record::new_mixed(l_brace, r_brace, attrs));
}
Some(_) => {
// レコードの中身は`lhs = rhs`や`lhs`になっている
// それらを改行やセミコロンで分割しているので`expr`
// ではなく`chunk`が呼ばれている
let next = self
.try_reduce_chunk(false, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
match next {
// lhs = rhsのとき
Expr::Def(def) => {
attrs.push(RecordAttrOrIdent::Attr(def));
}
// lhsのとき
Expr::Accessor(acc) => {
let ident = match acc {
Accessor::Ident(ident) => ident,
other => {
let err = ParseError::simple_syntax_error(
line!() as usize,
other.loc(),
);
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
};
attrs.push(RecordAttrOrIdent::Ident(ident));
}
_ => {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
}
_ => {
// self.restore(other);
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
}
}
try_reduce_normal_dict
複数型を持たない(等質)ときの辞書型のパース
fn try_reduce_normal_dict(
&mut self,
l_brace: Token,
first_key: Expr,
) -> ParseResult<NormalDict> {
debug_call_info!(self);
assert!(self.cur_is(Colon));
self.skip();
// とりあえずここで,一つ目のvalueをパースしておく
let value = self
.try_reduce_chunk(false, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
let mut kvs = vec![KeyValue::new(first_key, value)];
// 2要素目以降の内容を以降でパースして,kvsにプッシュしていく
loop {
match self.peek_kind() {
Some(Comma) => {
self.skip();
match self.peek_kind() {
// {1:"a",,とかのエラー
Some(Comma) => {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
// これが来たら{1:"a"}とかの一つの要素の辞書
Some(RBrace) => {
let dict = NormalDict::new(l_brace, self.lpop(), kvs);
debug_exit_info!(self);
return Ok(dict);
}
// これも改行で宣言用
Some(Newline) => {
self.skip();
}
_ => {}
}
// {key: value, ここまでは確定でパースされている
// 次のvalueのexprをパースする
let key = self
.try_reduce_expr(false, false, true, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
if self.cur_is(Colon) {
self.skip(); // colon
// {key: value, expr: chunk
// キーは式しか来れないが,バリューは塊をもってこれる
// 深い理由はまだわかっていないが感覚としてはわかる
let value = self
.try_reduce_chunk(false, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
kvs.push(KeyValue::new(key, value));
} else {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
Some(Newline | Indent | Dedent) => {
self.skip();
}
// 終了で辞書型に変換して返す
Some(RBrace) => {
let dict = NormalDict::new(l_brace, self.lpop(), kvs);
debug_exit_info!(self);
return Ok(dict);
}
// カンマや右括弧,スキップする改行,Indent,Dedent以外が来たら
// 構文エラーなので終了してErr(())にする
_ => {
break;
}
}
}
debug_exit_info!(self);
Err(())
}
try_reduce_set
セット型をパースする起点になるメソッド
- 長さ指定{Type: length}: {Nat; 10}
- 型指定{Type}: {Int}
- 重複無しの普通のセット型{elem1, elem2,..., elem_n}: {1, 2, 3}
他のと同様に改行もできる
{
1, 2,
3, 4,
} => {1, 2, 3, 4,} # だからカンマの後ろに}が来ても良い
container
で{elem
までパースされているので,次がセミコロン
かカンマ
かを判断してパースをする
fn try_reduce_set(&mut self, l_brace: Token, first_elem: Expr) -> ParseResult<Set> {
debug_call_info!(self);
// 型とサイズ指定のパース {Type; Length}
if self.cur_is(Semi) {
self.skip();
let len = self
.try_reduce_expr(false, false, false, false)
.map_err(|_| self.stack_dec(fn_name!()))?;
let r_brace = self.lpop();
debug_exit_info!(self);
return Ok(Set::WithLength(SetWithLength::new(
l_brace,
r_brace,
PosArg::new(first_elem),
len,
)));
}
let mut args = Args::new(vec![PosArg::new(first_elem)], vec![], None);
// 普通のセットか型指定をここでパースする
loop {
match self.peek_kind() {
Some(Comma) => {
self.skip();
match self.peek_kind() {
// {1,,などのエラー
Some(Comma) => {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
// {1, 2, 3, }などの終了
Some(RBrace) => {
let set = Set::Normal(NormalSet::new(l_brace, self.lpop(), args));
debug_exit_info!(self);
return Ok(set);
}
Some(Newline | Indent | Dedent) => {
self.skip();
}
_ => {}
}
// 集合型でキーワードは取れない
// 要素は位置引数となる
match self
.try_reduce_arg(false)
.map_err(|_| self.stack_dec(fn_name!()))?
{
PosOrKwArg::Pos(arg) => match arg.expr {
Expr::Set(Set::Normal(set)) if set.elems.paren.is_none() => {
args.extend_pos(set.elems.into_iters().0);
}
other => {
let pos = PosArg::new(other);
if !args.has_pos_arg(&pos) {
args.push_pos(pos);
}
}
},
// キーワードが来た時点でエラー
PosOrKwArg::Kw(arg) => {
let err = ParseError::simple_syntax_error(line!() as usize, arg.loc());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
}
Some(Newline | Indent | Dedent) => {
self.skip();
}
// 集合型に変換して返す
Some(RBrace) => {
let set = Set::Normal(NormalSet::new(l_brace, self.lpop(), args));
debug_exit_info!(self);
return Ok(set);
}
// カンマ,右括弧,スキップする改行,Indent,Dedent以外がいたら
// 構文エラーになるのでErr(())を返す
_ => {
break;
}
}
}
debug_exit_info!(self);
Err(())
}
try_reduce_nonempty_tuple
名前の通り,空じゃないことが保証された場合はこれが呼ばれる
括弧なし,改行,通常,キーワード引数など複数の条件が重なるのでかなりめんどくさい
引数のパースにも使われている
f 1
f 1, 2, 3
f(1, 2, 3)
f( # 改行の時は丸括弧がないといけない
1,
2,
3,
)
fn try_reduce_nonempty_tuple(
&mut self,
first_elem: PosOrKwArg,
line_break: bool,
) -> ParseResult<Tuple> {
debug_call_info!(self);
let mut args = match first_elem {
PosOrKwArg::Pos(pos) => Args::new(vec![pos], vec![], None),
PosOrKwArg::Kw(kw) => Args::new(vec![], vec![kw], None),
};
#[allow(clippy::while_let_loop)]
loop {
match self.peek_kind() {
Some(Comma) => {
self.skip();
while self.cur_is(Newline) && line_break {
self.skip();
}
// {1,,とかのエラー
if self.cur_is(Comma) {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
// 右括弧とDedentのときはこれで終了
} else if self.cur_is(Dedent) || self.cur_is(RParen) {
break;
}
// 位置引数かキーワード引数に分けれらる
match self
.try_reduce_arg(false)
.map_err(|_| self.stack_dec(fn_name!()))?
{
// ポジショナル引数の時にキーワード引数が既にパースされていれば
// ここでエラーとして検出する
PosOrKwArg::Pos(arg) if args.kw_is_empty() => match arg.expr {
Expr::Tuple(Tuple::Normal(tup)) if tup.elems.paren.is_none() => {
args.extend_pos(tup.elems.into_iters().0);
}
other => {
args.push_pos(PosArg::new(other));
}
},
// キーワード引数あった上でポジショナル引数があれば,エラーになる
PosOrKwArg::Pos(arg) => {
let err = ParseError::syntax_error(
line!() as usize,
arg.loc(),
switch_lang!(
"japanese" => "非デフォルト引数はデフォルト引数の後に指定できません",
"simplified_chinese" => "默认实参后面跟着非默认实参",
"traditional_chinese" => "默認實參後面跟著非默認實參",
"english" => "non-default argument follows default argument",
),
None,
);
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
// e.g. (x, y:=1) -> ...
// Syntax error will occur when trying to use it as a tuple
PosOrKwArg::Kw(arg) => {
args.push_kw(arg);
}
}
}
// ここでエラーを返さないのはなぜかはわからないので現在調査中
_ => {
break;
}
}
}
// タプル型は括弧がない場合があるのと,どんな式でも要素としてもてるので
// 終了条件が見つかりにくいので最後までパースしてしまう
let tup = Tuple::Normal(NormalTuple::new(args));
debug_exit_info!(self);
Ok(tup)
}
try_reduce_lit
一番小さなリテラルのパース
fn try_reduce_lit(&mut self) -> ParseResult<Literal> {
debug_call_info!(self);
debug_exit_info!(self);
match self.peek() {
Some(t) if t.category_is(TC::Literal) => Ok(Literal::from(self.lpop())),
// これが来たらパーサーのバグ
_ => {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
Err(())
}
}
}
try_reduce_string_interpolation
文字列内に\{}
を付けて計算結果などをそのまま文字列に代入することができるようにする
パディングとかの細かなフォーマティングの条件はまだないはず
"\{1} + \{1} is \{1+1}" # 1 + 1 is 2
割と複雑なパースの処理をしてる
単純なStrLit("sample"
)とは違いきちんとToken自体で分類されている
/// e.g. "left_of_string\{
StrInterpLeft,
/// e.g. }mid_of_string\{
StrInterpMid,
/// e.g. }end_of_string"
StrInterpRight,
なのでそのトークンの種類を見てここで一番左の括弧の前,括弧同士の中間(複数個あるかもしれないし,ないかもしれない),一番右の括弧の後ろそれぞれで分割された文字列としてみる
そのうえで中身は式としてパースして,それ以外を文字列としてパースをするということをしている
最終的にコメントにあるように,パースした式を文字列に変換するという式に直している
そして文字列同士で+
で結合するということをしている
"\{1} + \{1} is \{1+1}"
# 以下のように変換
str(1) + str(1) + str(1+1) # StrLitに変換
Expr::StrLit(...)
/// "...\{, expr, }..." ==> "..." + str(expr) + "..."
fn try_reduce_string_interpolation(&mut self) -> ParseResult<Expr> {
debug_call_info!(self);
// とりあえず`"...\{`ここまでパースする
let mut left = self.lpop();
left.content = Str::from(left.content.trim_end_matches("\\{").to_string() + "\"");
// StrInterpLeftからStrLitに変換
left.kind = StrLit;
// 再度文字列に変換する
let mut expr = Expr::Lit(Literal::from(left));
loop {
match self.peek() {
// 一番右の括弧なので終了条件に移る
Some(l) if l.is(StrInterpRight) => {
let mut right = self.lpop();
right.content =
Str::from(format!("\"{}", right.content.trim_start_matches('}')));
right.kind = StrLit;
// ここで右側のを文字の式に変換する
let right = Expr::Lit(Literal::from(right));
// 上記に示したように,今までのがlhsとして塊になっているはず
// 最後に右側を文字列にして一つの塊に直してそれを返す
let op = Token::new(
Plus,
"+",
right.ln_begin().unwrap(),
right.col_begin().unwrap(),
);
expr = Expr::BinOp(BinOp::new(op, expr, right));
debug_exit_info!(self);
return Ok(expr);
}
// 左側はループ前に終わらせているのでその中身の式をぱーする
Some(_) => {
let mid_expr = self.try_reduce_expr(true, false, false, false)?;
// str(...)の長さを求めて位置情報ほ更新
let str_func = Expr::local(
"str",
mid_expr.ln_begin().unwrap(),
mid_expr.col_begin().unwrap(),
);
// `str`関数の呼び出しに変換
let call = Call::new(
str_func,
None,
Args::new(vec![PosArg::new(mid_expr)], vec![], None),
);
// ここでも結合するので場所が変わる
// なので再度場所を更新している
let op = Token::new(
Plus,
"+",
call.ln_begin().unwrap(),
call.col_begin().unwrap(),
);
let bin = BinOp::new(op, expr, Expr::Call(call));
expr = Expr::BinOp(bin);
// 右の括弧が終わったら括弧の中間の文字列を処理することになる
// 上記で述べた通り,ない可能性もあるのでifになっている
if self.cur_is(StrInterpMid) {
let mut mid = self.lpop();
mid.content = Str::from(format!(
"\"{}\"",
mid.content.trim_start_matches('}').trim_end_matches("\\{")
));
mid.kind = StrLit;
let mid = Expr::Lit(Literal::from(mid));
let op = Token::new(
Plus,
"+",
mid.ln_begin().unwrap(),
mid.col_begin().unwrap(),
);
expr = Expr::BinOp(BinOp::new(op, expr, mid));
}
}
None => {
let err = ParseError::syntax_error(
line!() as usize,
expr.loc(),
switch_lang!(
"japanese" => "文字列補間の終わりが見つかりませんでした",
"simplified_chinese" => "未找到字符串插值结束",
"traditional_chinese" => "未找到字符串插值結束",
"english" => "end of string interpolation not found",
),
None,
);
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
}
}
try_reduce_stream_operator
関数型プログラミングでよく見かけるパイプ演算子のパース
一行に複数の関数を書けるので,処理が長くなる
基本的には関数名のパースにcall_or_acc
と引数のパースにopt_reduce_args
が呼ばれている
それらをループで終わりが来るまでひたすら回す感じ
rand = -1.0..1.0 |>.sample!()
log rand # 0.2597...
1+1*2 |>.times do log("a", end := "") # aaa
evens = 1..100 |>.iter() |>.filter i -> i % 2 == 0 |>.collect Array
# パイプライン演算子を使わずに実装する場合、
_evens = (1..100).iter().filter(i -> i % 2 == 0).collect(Array)
# または
__evens = 1..100 \
.iter() \
.filter i -> i % 2 == 0 \
.collect Array
引数のstackにはexpr
があることが前提になっている
fn try_reduce_stream_operator(&mut self, stack: &mut Vec<ExprOrOp>) -> ParseResult<()> {
debug_call_info!(self);
let op = self.lpop(); // |>
// 3つ以上の時にはスタックの中身が塊になっていないはずなので
// ここで一つの塊にする
while stack.len() >= 3 {
collect_last_binop_on_stack(stack);
}
// 二つの時にはきちんと塊になっていないので直前までのパースや変換にバグがあることが分かる
if stack.len() == 2 {
self.errs
.push(ParseError::compiler_bug(0, op.loc(), fn_name!(), line!()));
debug_exit_info!(self);
return Err(());
}
// このメソッド限定のエラー
fn get_stream_op_syntax_error(loc: Location) -> ParseError {
ParseError::syntax_error(
0,
loc,
switch_lang!(
"japanese" => "パイプ演算子の後には関数・メソッド・サブルーチン呼び出しのみが使用できます。",
"simplified_chinese" => "流操作符后只能调用函数、方法或子程序",
"traditional_chinese" => "流操作符後只能調用函數、方法或子程序",
"english" => "Only a call of function, method or subroutine is available after stream operator.",
),
None,
)
}
// ここから|>以降のパースをする
if matches!(self.peek_kind(), Some(Dot)) {
// obj |> .method(...)
let vis = self.lpop(); // ::か.
match self.lpop() {
symbol if symbol.is(Symbol) => {
let Some(ExprOrOp::Expr(obj)) = stack.pop() else {
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
};
if let Some(args) = self
.opt_reduce_args(false)
.transpose() // Option<Result>をResult<Option>にする
.map_err(|_| self.stack_dec(fn_name!()))?
{
let ident = Identifier::new(Some(vis), VarName::new(symbol));
let mut call = Expr::Call(Call::new(obj, Some(ident), args));
// TODO:
while let Some(res) = self.opt_reduce_args(false) {
let args = res.map_err(|_| self.stack_dec(fn_name!()))?;
call = call.call_expr(args);
}
stack.push(ExprOrOp::Expr(call));
} else {
self.errs.push(get_stream_op_syntax_error(obj.loc()));
debug_exit_info!(self);
debug_exit_info!(self);
return Err(());
}
}
// 関数じゃないからエラー
other => {
self.restore(other);
let err = self.skip_and_throw_syntax_err(caused_by!());
self.errs.push(err);
debug_exit_info!(self);
return Err(());
}
}
} else {
// 上のが.や::があるときで一般的な関数の呼び出しをここ以降でパースする
// 同上,ここで呼び出しにパースされるはず
let expect_call = self
.try_reduce_call_or_acc(false)
.map_err(|_| self.stack_dec(fn_name!()))?;
let Expr::Call(mut call) = expect_call else {
self.errs.push(get_stream_op_syntax_error(expect_call.loc()));
debug_exit_info!(self);
return Err(());
};
let ExprOrOp::Expr(first_arg) = stack.pop().unwrap() else {
self.errs
.push(ParseError::compiler_bug(0, call.loc(), fn_name!(), line!()));
debug_exit_info!(self);
return Err(());
};
// DODO:
call.args.insert_pos(0, PosArg::new(first_arg));
stack.push(ExprOrOp::Expr(Expr::Call(call)));
}
debug_exit_info!(self);
Ok(())
}
}
collect_last_binop_on_stack
二項演算子のときなどに使われていた
塊に直す作業をする
1 + 2 * 3 - 4 % 5 // 6
fn collect_last_binop_on_stack(stack: &mut Vec<ExprOrOp>) {
let rhs = enum_unwrap!(stack.pop(), Some:(ExprOrOp::Expr:(_)));
let op = enum_unwrap!(stack.pop(), Some:(ExprOrOp::Op:(_)));
let lhs = enum_unwrap!(stack.pop(), Some:(ExprOrOp::Expr:(_)));
let bin = BinOp::new(op, lhs, rhs);
stack.push(ExprOrOp::Expr(Expr::BinOp(bin)));
}
[WIP]マクロは勉強中なのでまだわからない
所感としては左辺のEnumと右辺のexprのenumが一致する場合にその一回または複数回分深くなったのを具体化して取り出している感じかなと思う
macro_rules! enum_unwrap {
($ex: expr, $Enum: path $(,)*) => {{
if let $Enum(res) = $ex { res } else { $crate::switch_unreachable!() }
}};
($ex: expr, $Enum: path :( $Cons: path :(_) ) $(,)*) => {{
if let $Enum($Cons(res)) = $ex { res } else { $crate::switch_unreachable!() }
}};
($ex: expr, $Enum: path :( $Cons: path :( $Cons2: path :(_) ) ) $(,)*) => {{
if let $Enum($Cons($Cons2(res))) = $ex { res } else { $crate::switch_unreachable!() }
}};
// X::A{a, b}
($ex: expr, $Enum: path {$($fields: ident $(,)*)*}) => {{
if let $Enum{$($fields,)*} = $ex { ($($fields,)*) } else { $crate::switch_unreachable!() }
}};
}
終わりに
一応(1)と今回の(2)と合わせて軽くではあるがParserのメソッドを一通り説明した
こんな複雑なのをよく一人で作れたなあとShibaさんの偉大さが身に染みてくる
説明できなかったり曖昧な箇所があったので,しっかりと読み込んだり質問して理解できるように努めたい
余談
なぜこれを作成したのかというと,エラーの分類をしているのが理由となっている
それともう一つの大きな理由がErgのtree-sitterを作成しようとしていたからである
というのも,今回のパーサー解説がここまで長くなったからわかると思うが、パーサーが複雑すぎて書けなかったからである
Haskellのtree-sitterを参考に作ってみようとして難しすぎて諦めた
これを機に作成を再開しようかなあと思っているが,正直できる気がしない