tree-sitter::Node の is_error, is_missing, has_error の違い
tree-sitter の Rust binding では、 Node に is_error, is_missing, has_error という3つのメソッドが生えている
それぞれの違いは以下
Node::is_error
そのノード自体が Error ノードかどうか
エラー回復によって生成されるノード
特に、文法に一致しない内容のためにスキップされたもの
Node::is_missing
そのノード自体が Missing ノードかどうか
エラー回復によって生成されるノード
特に、必要な要素が不足しているために挿入されたもの
Node::has_error
そのノード自体、またはその内部のどこかに Error または missing ノードが含まれているかどうか
ルートでこれを呼べば、構文に沿った内容かどうかわかる
参考
What are the relationships between is_missing, is_error, has_error in the Rust binding? · Issue #396 · tree-sitter/tree-sitter · GitHub
観測方法
tree-sitter parse だと ERROR は出てくるが、MISSING は出てこない
こういう grammar があるとして
code:grammar.js
module.exports = grammar({
name: 'calc',
extras: $ => [
/\s|\\\r?\n/,
$.block_comment
],
rules: {
source_file: $ => $._statement,
block_comment: $ => token(seq('{', /^}*/, '}')),
_statement: $ => choice(
$.assignment,
$._expression
),
assignment: $ => prec(5, seq(field('lhs', $.identifier), '=', field('rhs', $._expression))),
_expression: $ => choice(
$.identifier,
$.number,
$.unary_expression,
$.binary_expression,
$.parentheses_expression
),
parentheses_expression: $ => seq('(', field('expr', $._expression), ')'),
unary_expression: $ => prec(4, choice(
seq(field('op', '+'), field('expr', $._expression)),
seq(field('op', '-'), field('expr', $._expression))
)),
binary_expression: $ => choice(
prec.left(2, seq(field('lhs', $._expression), field('op', '*'), field('rhs', $._expression))),
prec.left(2, seq(field('lhs', $._expression), field('op', '/'), field('rhs', $._expression))),
prec.left(1, seq(field('lhs', $._expression), field('op', '+'), field('rhs', $._expression))),
prec.left(1, seq(field('lhs', $._expression), field('op', '-'), field('rhs', $._expression))),
prec.left(3, seq(field('lhs', $._expression), field('op', '**'), field('rhs', $._expression))),
),
number: $ => /\d+/,
identifier: $ => /a-z_+/
}
});
tree-sitter parse では Error は観測できるが
code:tree-sitter_parse_error
:) echo "1
+
a=
" | ts parse
(source_file 0, 0 - 4, 0
(binary_expression 0, 0 - 2, 1
lhs: (number 0, 0 - 0, 1)
rhs: (identifier 2, 0 - 2, 1))
(ERROR 2, 1 - 2, 2))
stdin Parse: 0.04 ms 182 bytes/ms (ERROR 2, 1 - 2, 2)
MISSING は観測できない
code:tree-sitter_parse_missing
:) echo "1
+
" | ts parse
(source_file 0, 0 - 4, 0
(binary_expression 0, 0 - 1, 1
lhs: (number 0, 0 - 0, 1)
rhs: (number 1, 1 - 1, 1)))
stdin Parse: 0.03 ms 172 bytes/ms (MISSING number 1, 1 - 1, 1)
MISSING を観測したい場合は binding を触る必要がある。
code:tree_sitter_binding_missing
:) echo "1
+
" | cargo r -q --
has_error: true
source_file (0, 0)-(4, 0)
binary_expression (0, 0)-(1, 1)
number "1" (0, 0)-(0, 1)
+ (1, 0)-(1, 1)
number MISSING "" (1, 1)-(1, 1)
Rust の場合、次の感じになる
code:main.rs
use std::fs::read_to_string;
use std::io::{self, Read};
use tree_sitter::TreeCursor;
use tree_sitter_practice::language;
fn main() {
let language = language();
let mut parser = tree_sitter::Parser::new();
parser.set_language(language).unwrap();
let src = match std::env::args().nth(1) {
Some(path) if path != "-" => read_to_string(&path).expect("failed to read file"),
_ => {
let mut buffer = String::new();
io::stdin()
.read_to_string(&mut buffer)
.expect("failed to read from stdin");
buffer
}
};
let tree = parser.parse(&src, None).unwrap();
println!("has_error: {}", tree.root_node().has_error());
let mut cursor = tree.walk();
visit(&mut cursor, 0, &src);
}
const UNIT: usize = 2;
fn visit(cursor: &mut TreeCursor, depth: usize, src: &str) {
(0..(depth * UNIT)).for_each(|_| print!(" "));
print!("{}", cursor.node().kind());
if cursor.node().is_error() {
print!(" ERROR");
}
if cursor.node().is_missing() {
print!(" MISSING");
}
if cursor.node().child_count() == 0 && cursor.node().kind().chars().any(|c| c.is_lowercase()) {
print!(" \"{}\"", cursor.node().utf8_text(src.as_bytes()).unwrap());
}
println!(
" {}-{}",
cursor.node().start_position(),
cursor.node().end_position()
);
if cursor.goto_first_child() {
visit(cursor, depth + 1, src);
while cursor.goto_next_sibling() {
visit(cursor, depth + 1, src);
}
cursor.goto_parent();
}
}