いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-600 (Tree-sitterメモ-04)(シンタックスエラーの出力)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。前回までのメモは関連記事にかかれている ブログ記事まとめ よりどうぞ。

今回は、シンタックスエラーの出力について。

Parseメソッドの結果から取得できるNodeは、自身のエラー状態を持っています。もし、構文エラーが発生している場合はエラーありとなっています。

ルートノードにて HasErrorを呼び出して、エラーが存在すると判定できたら、再帰でノードを下って処理します。

該当ノードから、StartPositionEndPositionを使うと、何行目の何列目なのかが取得できるので、この情報を元のソースコードと合わせると該当箇所がコード上でも特定できます。

ただし、Tree-sitterはコンパイラでは無いので、エラーとして検出されるのはあくまでも「シンタックスエラー」と判定されるもののみとなります。

例として再帰関数は以下のように実装できますね。

func walk(node *tree_sitter.Node, callback func(*tree_sitter.Node)) {
    callback(node)
    for i := uint(0); i < node.ChildCount(); i++ {
        walk(node.Child(i), callback)
    }
}

こんな感じで使えます。

       walk(root, func(n *tree_sitter.Node) {
            if n.IsError() || n.IsMissing() {
                var (
                    start = n.StartPosition()
                    end   = n.EndPosition()
                )
                log.Printf("エラー: %d行%d列 - %d行%d列", start.Row+1, start.Column+1, end.Row+1, end.Column+1)

                // コード行表示
                var (
                    scanner = bufio.NewScanner(bytes.NewReader(srcCode))

                    line    string
                    numLine int
                )
                for ; scanner.Scan(); numLine++ {
                    if numLine == int(start.Row) {
                        line = scanner.Text()
                        log.Printf("  問題のコード: %s\n", line)
                        break
                    }
                }
            }
        })

ドキュメントを見ると、TreeCursorというカーソルオブジェクトが存在していて、Node.Walkなどから取得できるのですが、再帰的なウォーキングをしてくれるものではないため、上のように普通に自分で再帰処理書いたほうが楽なのでそうしてます。

サンプル

main.go

package main

import (
    "bufio"
    "bytes"
    "errors"
    "log"

    tree_sitter "github.com/tree-sitter/go-tree-sitter"
    tree_sitter_c "github.com/tree-sitter/tree-sitter-c/bindings/go"
)

const (
    C_CODE = `#include <stdio.h>
int main(void) {
  int x
  int y = 10;
  if {
  }
  int  = 20;

  printf("hello %d\n", z)
  return 0;
}`
)

var (
    srcCode = []byte(C_CODE)
)

func main() {
    log.SetFlags(0)

    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    // パーサーの生成と言語の設定
    var (
        p   = tree_sitter.NewParser()
        l   = tree_sitter.NewLanguage(tree_sitter_c.Language())
        err error
    )
    defer p.Close()
    if err = p.SetLanguage(l); err != nil {
        return err
    }

    // 解析を実施しツリー取得
    var (
        tree = p.Parse(srcCode, nil)
    )
    if tree == nil {
        return errors.New("解析に失敗: Parse()")
    }
    defer tree.Close()

    // エラーが存在するか検査
    var (
        root = tree.RootNode()
    )
    if root.HasError() {
        walk(root, func(n *tree_sitter.Node) {
            if n.IsError() || n.IsMissing() {
                var (
                    start = n.StartPosition()
                    end   = n.EndPosition()
                )
                log.Printf("エラー: %d行%d列 - %d行%d列", start.Row+1, start.Column+1, end.Row+1, end.Column+1)

                // コード行表示
                var (
                    scanner = bufio.NewScanner(bytes.NewReader(srcCode))

                    line    string
                    numLine int
                )
                for ; scanner.Scan(); numLine++ {
                    if numLine == int(start.Row) {
                        line = scanner.Text()
                        log.Printf("  問題のコード: %s\n", line)
                        break
                    }
                }
            }
        })
    }

    return nil
}

func walk(node *tree_sitter.Node, callback func(*tree_sitter.Node)) {
    callback(node)
    for i := uint(0); i < node.ChildCount(); i++ {
        walk(node.Child(i), callback)
    }
}

実行結果

$ task
task: [default] go run .
エラー: 37列 - 37列
  問題のコード:         int x
エラー: 52列 - 54列
  問題のコード:         if {
エラー: 75列 - 75列
  問題のコード:         int  = 20;
エラー: 925列 - 925列
  問題のコード:         printf("hello %d\n", z)

参考情報

tree-sitter.github.io

github.com

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。