Open12

Rust Sitter学習メモ

Pocket7878Pocket7878

簡単な四則演算の構文

READMEではgrammarに名前を指定していないが、実際には指定しないとエラーになる
こまかい所だけど一応PRで出しておいた
https://github.com/hydro-project/rust-sitter/pull/1

arithmetic.rs
#[rust_sitter::grammar("arithmetic")]
pub mod grammar {
    #[rust_sitter::language]
    #[derive(Debug)]
    pub enum Expr {
        Number(
            #[rust_sitter::leaf(pattern = r"\d+", transform = |v| v.parse().unwrap())]
            u32
        ),
        #[rust_sitter::prec_left(1)]
        Add(
            Box<Expr>,
            #[rust_sitter::leaf(text = "+")] (),
            Box<Expr>),
        #[rust_sitter::prec_left(1)]
        Sub(
            Box<Expr>,
            #[rust_sitter::leaf(text = "-")] (),
            Box<Expr>),
        #[rust_sitter::prec_left(2)]
        Multiply(
            Box<Expr>,
            #[rust_sitter::leaf(text = "*")] (),
            Box<Expr>),
        #[rust_sitter::prec_left(2)]
        Div(
            Box<Expr>,
            #[rust_sitter::leaf(text = "/")] (),
            Box<Expr>),
    }
}
main.rs
mod arithmetic;

fn main() {
    dbg!(arithmetic::grammar::parse("1+2*3-4/5"));
}
[src/main.rs:5] arithmetic::grammar::parse("1+2*3-4/5") = Ok(
    Sub(
        Add(
            Number(
                1,
            ),
            (),
            Multiply(
                Number(
                    2,
                ),
                (),
                Number(
                    3,
                ),
            ),
        ),
        (),
        Div(
            Number(
                4,
            ),
            (),
            Number(
                5,
            ),
        ),
    ),
)
Pocket7878Pocket7878

ソースを読んで、マクロでパース結果の解析用のRustコードを生成する箇所でEnumの場合は
Unnamedなフィールドのみの想定になっている構文が生成されていそうだったので、その箇所の分岐を追加してPRに出してマージされた

https://github.com/hydro-project/rust-sitter/pull/2

ので以下のエラーは解消済み

こういう、enumの構文はサポートしていそうだが

pub enum Foo {
  Baz(#[rust_sitter::leaf(pattern = r"\d+", transform = |v| v.parse().unwrap())] u32),
}

こういう構文にするとエラーになる

pub enum Foo {
  Baz {
    #[rust_sitter::leaf(pattern = r"\d+", transform = |v| v.parse().unwrap())]
    value: u32
  },
}

発生するエラー:

error: custom attribute panicked
 --> src/arithmetic.rs:1:1
  |
1 | #[rust_sitter::grammar("arithmetic")]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: message: expected `,`
Pocket7878Pocket7878

TreeSitterのREPEATを使いつつ無駄なノードを捨てるために、Vecを利用する場合はフィールド名が必須になるようで、以下のようにEnumの中に名前無しでVecが入っている構文も今はサポートしていないよう

こちらも匿名フィールドの場合は、そのフィールドのインデックスをつかった名前の方にフォールバックするようにするPRを出してマージされたので解決済み
https://github.com/hydro-project/rust-sitter/pull/3

#[rust_sitter::grammar("example")]
pub mod grammar {
		#[derive(Debug)]
		pub struct Foo {
			#[rust_sitter::leaf(pattern = r"[a-z][a-zA-Z_]*", transform = |v| v.to_string())]
			value: String,
		}

		#[rust_sitter::language]
		#[derive(Debug)]
		pub enum Prog {
			Bar(
				#[rust_sitter::repeat(non_empty = true)]
				Vec<Foo>
			)
		}
}

ここでエラーになる
https://github.com/hydro-project/rust-sitter/blob/676cf5449009fc3477be7729870e57df4099e1fe/tool/src/lib.rs#L108

Pocket7878Pocket7878

そもそもやりたい構文がTree Sitterではどう書けるのかというのを移植してみた

module.exports = grammar({
  name: 'FuncLang',
  rules: {
    source_file: $ => repeat($._definition),

    _definition: $ => choice(
      $.defn,
      $.data
    ),

    defn: $ => seq(
      'defn',
      $.lid,
      optional($.lowercase_params),
      '=',
      '{',
      $.a_add,
      '}'
    ),

    lowercase_params: $ => repeat1($.lid),
    uppercase_params: $ => repeat1($.uid),

    a_add: $ => choice(
      prec.left(2, seq($.a_add, '+', $.a_mul)),
      prec.left(1, seq($.a_add, '-', $.a_mul)),
      $.a_mul
    ),

    a_mul: $ => choice(
      prec.left(2, seq($.a_mul, '*', $.app)),
      prec.left(1, seq($.a_mul, '/', $.app)),
      $.app
    ),

    app: $ => repeat1($._app_base),
    _app_base: $ => choice(
      $.int,
      $.lid,
      $.uid,
      seq('(', $.a_add, ')'),
      $.case
    ),
    case: $ => seq(
      'case',
      $.a_add,
      'of',
      '{',
      repeat1($.branch),
      '}'
    ),
    branch: $ => seq(
      $.pattern,
      '->',
      '{',
      $.a_add,
      '}'
    ),
    pattern: $ => choice(
      $.lid,
      seq($.uid, optional($.lowercase_params))
    ),
    data: $ => seq(
      'data',
      $.uid,
      '=',
      '{',
      $._constructors, 
      '}'
    ),
    _constructors: $ => choice(
      seq($._constructors, ',', $.constructor),
      $.constructor
    ),
    constructor: $ => seq(
      $.uid,
      optional($.uppercase_params)
    ),
    lid: $ => /[a-z][a-zA-z]*/,
    uid: $ => /[A-Z][a-zA-z]*/,
    int: $ => /\d+/,
  }
});

これをRust Sitterに移植してみる

Pocket7878Pocket7878

多少構造を圧縮しつつ移植してみるとこんな感じ

#[rust_sitter::grammar("func_lang")]
pub mod grammar {
		#[rust_sitter::language]
		#[derive(Debug)]
		pub struct Program {
			definitions: Vec<Definition>,
		}

		#[derive(Debug)]
		pub enum Definition {
			Defn {
				#[rust_sitter::leaf(text = "defn")]
				_defn: (),
				name: LID,
				arguments: Option<LowercaseParams>,
				#[rust_sitter::leaf(text = "=")]
				_equal: (),
				#[rust_sitter::leaf(text = "{")]
				_open_brace: (),
				body: AstAdd,
				#[rust_sitter::leaf(text = "}")]
				_close_brace: (),
			},
			Data {
				#[rust_sitter::leaf(text = "data")]
				_data: (),
				name: UID,
				#[rust_sitter::leaf(text = "=")]
				_equal: (),
				#[rust_sitter::leaf(text = "{")]
				_open_brace: (),
				#[rust_sitter::repeat(non_empty = true)]
				#[rust_sitter::delimited(
					#[rust_sitter::leaf(text = ",")]
					()
				)]
				constructors: Vec<Constructor>,
				#[rust_sitter::leaf(text = "}")]
				_close_brace: (),
			}
		}

		#[derive(Debug)]
		pub struct LowercaseParams {
			#[rust_sitter::repeat(non_empty = true)]
			params: Vec<LID>,
		}

		#[derive(Debug)]
		pub struct UppercaseParams {
			#[rust_sitter::repeat(non_empty = true)]
			params: Vec<UID>,
		}

		#[derive(Debug)]
		pub enum AstAdd {
			#[rust_sitter::prec_left(2)]
			Plus(
				Box<AstAdd>,
				#[rust_sitter::leaf(text = "+")] (),
				AstMul,
			),
			#[rust_sitter::prec_left(1)]
			Minus(
				Box<AstAdd>,
				#[rust_sitter::leaf(text = "-")] (),
				AstMul,
			),
			AstMul(AstMul),
		}

		#[derive(Debug)]
		pub enum AstMul {
			#[rust_sitter::prec_left(2)]
			Times(
				Box<AstMul>,
				#[rust_sitter::leaf(text = "*")] (),
				App,
			),
			#[rust_sitter::prec_left(1)]
			Div(
				Box<AstMul>,
				#[rust_sitter::leaf(text = "/")] (),
				App,
			),
			App(App),
		}

		#[derive(Debug)]
		pub struct App {
			#[rust_sitter::repeat(non_empty = true)]
			app_bases: Vec<AppBase>,
		}

		#[derive(Debug)]
		pub enum AppBase {
			Int(#[rust_sitter::leaf(pattern = r"\d+", transform = |v| v.parse().unwrap())] u32),
			LID(LID),
			UID(UID),
			Parenthesized(
				#[rust_sitter::leaf(text = "(")] (),
				AstAdd,
				#[rust_sitter::leaf(text = ")")] ()
			),
			Case {
				#[rust_sitter::leaf(text = "case")]
				_case: (),
				value: AstAdd,
				#[rust_sitter::leaf(text = "of")]
				_of: (),
				#[rust_sitter::leaf(text = "{")]
				_open_brace: (),
				#[rust_sitter::repeat(non_empty = true)]
				branches: Vec<Branch>,
				#[rust_sitter::leaf(text = "}")]
				_close_brace: (),
			}
		}

		#[derive(Debug)]
		pub struct Branch {
			pattern: Pattern,
			#[rust_sitter::leaf(text = "->")]
			_arrow: (),
			#[rust_sitter::leaf(text = "{")]
			_open_brace: (),
			body: AstAdd,
			#[rust_sitter::leaf(text = "}")]
			_close_brace: (),
		}

		#[derive(Debug)]
		pub enum Pattern {
			LID(LID),
			Constructor {
				constructor: UID,
				params: Option<LowercaseParams>,
			},
		}

		#[derive(Debug)]
		pub struct Constructor {
			name: UID,
			params: Option<UppercaseParams>,
		}

		#[derive(Debug)]
		pub struct LID {
			#[rust_sitter::leaf(pattern = r"[a-z][a-zA-Z]*", transform = |v| v.to_string())]
			value: String,
		}

		#[derive(Debug)]
		pub struct UID {
			#[rust_sitter::leaf(pattern = r"[A-Z][a-zA-Z]*", transform = |v| v.to_string())]
			value: String,
		}

    #[rust_sitter::extra]
    struct Whitespace {
        #[rust_sitter::leaf(pattern = r"\s")]
        _whitespace: (),
    }
}
Pocket7878Pocket7878

すこし複雑なものもちゃんとパースできる:

mod func_lang;

fn main() {
    dbg!(func_lang::grammar::parse(r#"
    data List = { Nil, Cons Int List }
    defn f x = { 
        case x of { 
            Nil -> { 0 }
            Cons x xs -> { x }
        }
    }
"#)).expect("Failed to parse");
}
[src/main.rs:7] func_lang::grammar::parse(r#"
    data List = { Nil, Cons Int List }
    defn f x = { 
        case x of { 
            Nil -> { 0 }
            Cons x xs -> { x }
        }
    }
"#) = Ok(
    Program {
        definitions: [
            Data {
                _data: (),
                name: UID {
                    value: "List",
                },
                _equal: (),
                _open_brace: (),
                constructors: [
                    Constructor {
                        name: UID {
                            value: "Nil",
                        },
                        params: None,
                    },
                    Constructor {
                        name: UID {
                            value: "Cons",
                        },
                        params: Some(
                            UppercaseParams {
                                params: [
                                    UID {
                                        value: "Int",
                                    },
                                    UID {
                                        value: "List",
                                    },
                                ],
                            },
                        ),
                    },
                ],
                _close_brace: (),
            },
            Defn {
                _defn: (),
                name: LID {
                    value: "f",
                },
                arguments: Some(
                    LowercaseParams {
                        params: [
                            LID {
                                value: "x",
                            },
                        ],
                    },
                ),
                _equal: (),
                _open_brace: (),
                body: AstMul(
                    App(
                        App {
                            app_bases: [
                                Case {
                                    _case: (),
                                    value: AstMul(
                                        App(
                                            App {
                                                app_bases: [
                                                    LID(
                                                        LID {
                                                            value: "x",
                                                        },
                                                    ),
                                                ],
                                            },
                                        ),
                                    ),
                                    _of: (),
                                    _open_brace: (),
                                    branches: [
                                        Branch {
                                            pattern: Constructor {
                                                constructor: UID {
                                                    value: "Nil",
                                                },
                                                params: None,
                                            },
                                            _arrow: (),
                                            _open_brace: (),
                                            body: AstMul(
                                                App(
                                                    App {
                                                        app_bases: [
                                                            Int(
                                                                0,
                                                            ),
                                                        ],
                                                    },
                                                ),
                                            ),
                                            _close_brace: (),
                                        },
                                        Branch {
                                            pattern: Constructor {
                                                constructor: UID {
                                                    value: "Cons",
                                                },
                                                params: Some(
                                                    LowercaseParams {
                                                        params: [
                                                            LID {
                                                                value: "x",
                                                            },
                                                            LID {
                                                                value: "xs",
                                                            },
                                                        ],
                                                    },
                                                ),
                                            },
                                            _arrow: (),
                                            _open_brace: (),
                                            body: AstMul(
                                                App(
                                                    App {
                                                        app_bases: [
                                                            LID(
                                                                LID {
                                                                    value: "x",
                                                                },
                                                            ),
                                                        ],
                                                    },
                                                ),
                                            ),
                                            _close_brace: (),
                                        },
                                    ],
                                    _close_brace: (),
                                },
                            ],
                        },
                    ),
                ),
                _close_brace: (),
            },
        ],
    },
)
Pocket7878Pocket7878

Boxはマクロの処理側等で組み込みでサポートしているけど、もっと多くの種類のスマートポインタをサポートできないかなぁとおもってこんな風にしてみたが...

    let mut wrapped_type = typ.clone();
    let mut wrapper_fn = syn::parse_quote!((|x: #wrapped_type| { x })(x));
    for wrapper_typ in wrapper_types.iter().rev() {
        wrapper_fn = syn::parse_quote! {
            (|x: #wrapped_type| { 
                #wrapper_typ::new(x)
            })(#wrapper_fn)
        };
        wrapped_type = syn::parse_quote!(#wrapper_typ<#wrapped_type>);
    }
    wrapper_fn = syn::parse_quote! {
        (|x: #typ| { #wrapper_fn })
    };

ちょっとゴチャゴチャしてるけど、クロージャをつくっては即値をあたえる事でラップするようなコードが生成される

fn extract_Program_value(
		node: Option<rust_sitter::Node>,
		source: &[u8],
) -> Rc<RefCell<Number>> {
		(|x: Number| {
				(|x: RefCell<Number>| Rc::new(x))((|x: Number| RefCell::new(x))(
						(|x: Number| x)(x),
				))
		})(Number::extract(node.unwrap(), source))
}

ただ、インポート時のエイリアスとかそういう事を考えるとこれは複雑になりすぎそうだし、直近僕の手元では特にそこまで色々なスマートポインタにくるみたい気持ちもないから(必要だったらパース後に全体を包めば良いし...)これはお蔵入りかな

一応GitHubにだけおいておく
https://github.com/pocket7878/rust-sitter/tree/feature-support-more-smart-pointers

Pocket7878Pocket7878

今はenum, struct以外の構文要素をgrammerのmodule内に置けない(置くとエラーになる)ので
名前をフルで修飾する必要はあるが、こんな形で外部のパッケージの型にもパースできる
(このサンプルは時刻なので正規表現が複雑)

example.rs
#[rust_sitter::grammar("example")]
pub mod grammar {
		#[rust_sitter::language]
		#[derive(Debug)]
		pub struct Schedule {
			#[rust_sitter::leaf(
				pattern = r"([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))",
				transform = |v| {
					time::OffsetDateTime::parse(&v.to_string(), &time::format_description::well_known::iso8601::Iso8601::DEFAULT).unwrap()
				}
			)]
			value: time::OffsetDateTime,
		}
}
main.rs
mod example;

fn main() {
    let schedule = example::grammar::parse("2006-01-02T15:04:05.999Z").expect("failed to parse");
    dbg!(schedule);
}
[src/main.rs:19] schedule = Schedule {
    value: OffsetDateTime {
        local_datetime: PrimitiveDateTime {
            date: Date {
                year: 2006,
                ordinal: 2,
            },
            time: Time {
                hour: 15,
                minute: 4,
                second: 5,
                nanosecond: 999000000,
            },
        },
        offset: UtcOffset {
            hours: 0,
            minutes: 0,
            seconds: 0,
        },
    },
}

長い変換処理自体は別のパッケージに追いておく事でこういう風にも書ける

parse_util.rs
pub fn parse_iso8601(str: &str) -> time::OffsetDateTime {
    time::OffsetDateTime::parse(
        str,
        &time::format_description::well_known::iso8601::Iso8601::DEFAULT,
    )
    .unwrap()
}
example.rs
#[rust_sitter::grammar("example")]
pub mod grammar {
		#[rust_sitter::language]
		#[derive(Debug)]
		pub struct Schedule {
			#[rust_sitter::leaf(
				pattern = r"([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))",
				transform = |v| { crate::parse_util::parse_iso8601(v) }
			)]
			value: time::OffsetDateTime,
		}
}
Pocket7878Pocket7878

構文のノードにimplしたい場合も、grammerの中に直接置くとエラーになるので、
別の所に書く事になる

example.rs
#[rust_sitter::grammar("example")]
pub mod grammar {
		#[rust_sitter::language]
		#[derive(Debug, Clone)]
		pub struct Number {
			#[rust_sitter::leaf(
				pattern = r"\d+",
				transform = |v| { v.parse().unwrap() },
			)]
			pub value: u32,
		}
}

impl grammar::Number {
	pub fn increment(&mut self) {
		self.value += 1;
	}
}
Pocket7878Pocket7878

こんな感じでgrammarの外に色々定義してgrammarから使ったり、grammarのNodeにたいして色々定義したりできる

example.rs
#[rust_sitter::grammar("example")]
pub mod grammar {
		#[rust_sitter::language]
		#[derive(Debug, Clone)]
		pub struct Number {
			#[rust_sitter::leaf(
				pattern = r"\d+",
				transform = |v| { super::parse_u32(v) },
			)]
			pub value: u32,
		}
}

pub trait Incrementable {
	fn increment(&mut self);
}

impl Incrementable for grammar::Number {
	fn increment(&mut self) {
		self.value += 1;
	}
}

fn parse_u32(str: &str) -> u32 {
	str.parse().unwrap()
}

ちょっと面倒な気もするが、grammarが構文の定義だけに強制的に制限され、他の関数とか構造を複雑にもちこまれないのでこれはこれで良いのかもしれない