diff --git a/.gitignore b/.gitignore index 04c2fb3..94ffef2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ +.tox build dist *.pyc *.egg-info *.so __pycache__ tests/fixtures diff --git a/.travis.yml b/.travis.yml index 87bb284..f8d161f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,11 @@ language: python python: 3.6 +install: "pip install tox" script: - script/fetch-fixtures - - script/test + - tox branches: only: - master diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8ee8b16 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include README.md +include LICENSE +prune tree_sitter/core +graft tree_sitter/core/lib/src +graft tree_sitter/core/lib/include/tree_sitter +include tree_sitter/core/lib/utf8proc/*.c +include tree_sitter/core/lib/utf8proc/*.h diff --git a/Manifest.in b/Manifest.in deleted file mode 100644 index 3f2d8c2..0000000 --- a/Manifest.in +++ /dev/null @@ -1,5 +0,0 @@ -include README.md -include LICENSE -include tree_sitter/core/lib/utf8proc/* -include tree_sitter/core/lib/src/* -include tree_sitter/core/lib/include/tree_sitter/* diff --git a/setup.py b/setup.py index 0194f82..dae813a 100644 --- a/setup.py +++ b/setup.py @@ -1,53 +1,48 @@ """ Py-Tree-sitter """ - -import os import platform -from setuptools import setup, Extension +from os import path + +from setuptools import Extension +from setuptools import setup -base_dir = os.path.dirname(__file__) -with open(os.path.join(base_dir, 'README.md')) as f: - long_description = f.read() +with open(path.join(path.dirname(__file__), "README.md")) as f: + LONG_DESCRIPTION = f.read() setup( - name = "tree_sitter", - version = "0.0.8", - maintainer = "Max Brunsfeld", - maintainer_email = "maxbrunsfeld@gmail.com", - author = "Max Brunsfeld", - author_email = "maxbrunsfeld@gmail.com", - url = "https://github.com/tree-sitter/py-tree-sitter", - license = "MIT", - platforms = ["any"], - python_requires = ">=3.3", - description = "Python bindings to the Tree-sitter parsing library", - long_description = long_description, - long_description_content_type = "text/markdown", - classifiers = [ + name="tree_sitter", + version="0.0.8", + maintainer="Max Brunsfeld", + maintainer_email="maxbrunsfeld@gmail.com", + author="Max Brunsfeld", + author_email="maxbrunsfeld@gmail.com", + url="https://github.com/tree-sitter/py-tree-sitter", + license="MIT", + platforms=["any"], + python_requires=">=3.3", + description="Python bindings to the Tree-sitter parsing library", + long_description=LONG_DESCRIPTION, + long_description_content_type="text/markdown", + classifiers=[ "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Compilers", "Topic :: Text Processing :: Linguistic", ], - packages = ['tree_sitter'], - ext_modules = [ + packages=["tree_sitter"], + ext_modules=[ Extension( - "tree_sitter_binding", - [ - "tree_sitter/core/lib/src/lib.c", - "tree_sitter/binding.c", - ], - include_dirs = [ + "tree_sitter.binding", + ["tree_sitter/core/lib/src/lib.c", "tree_sitter/binding.c"], + include_dirs=[ "tree_sitter/core/lib/include", "tree_sitter/core/lib/utf8proc", ], - extra_compile_args = ( - ['-std=c99'] if platform.system() != 'Windows' else None - ) + extra_compile_args=( + ["-std=c99"] if platform.system() != "Windows" else None + ), ) ], - project_urls = { - 'Source': 'https://github.com/tree-sitter/py-tree-sitter', - } + project_urls={"Source": "https://github.com/tree-sitter/py-tree-sitter"}, ) diff --git a/tests/__init__.py b/tests/test_tree_sitter.py similarity index 71% rename from tests/__init__.py rename to tests/test_tree_sitter.py index 0907602..b8bb73b 100644 --- a/tests/__init__.py +++ b/tests/test_tree_sitter.py @@ -1,207 +1,228 @@ +# pylint: disable=missing-docstring + +import re import unittest -import os.path as path -from tree_sitter import Parser, Language +from os import path + +from tree_sitter import Language +from tree_sitter import Parser LIB_PATH = path.join("build", "languages.so") -Language.build_library(LIB_PATH, [ - path.join("tests", "fixtures", "tree-sitter-python"), - path.join("tests", "fixtures", "tree-sitter-javascript"), -]) +Language.build_library( + LIB_PATH, + [ + path.join("tests", "fixtures", "tree-sitter-python"), + path.join("tests", "fixtures", "tree-sitter-javascript"), + ], +) PYTHON = Language(LIB_PATH, "python") JAVASCRIPT = Language(LIB_PATH, "javascript") +def _collapse_ws(string): + return re.sub(r"\s+", " ", string).strip() + + class TestTreeSitter(unittest.TestCase): def test_set_language(self): parser = Parser() parser.set_language(PYTHON) tree = parser.parse(b"def foo():\n bar()") self.assertEqual( tree.root_node.sexp(), - "(module (function_definition " - "name: (identifier) " - "parameters: (parameters) " - "body: (block (expression_statement (call " - "function: (identifier) " - "arguments: (argument_list))))))" + _collapse_ws( + """(module (function_definition + name: (identifier) + parameters: (parameters) + body: (block (expression_statement (call + function: (identifier) + arguments: (argument_list))))))""" + ), ) parser.set_language(JAVASCRIPT) tree = parser.parse(b"function foo() {\n bar();\n}") self.assertEqual( tree.root_node.sexp(), - "(program (function_declaration " - "name: (identifier) " - "parameters: (formal_parameters) " - "body: (statement_block " - "(expression_statement (call_expression function: (identifier) arguments: (arguments))))))" + _collapse_ws( + """(program (function_declaration + name: (identifier) + parameters: (formal_parameters) + body: (statement_block + (expression_statement + (call_expression + function: (identifier) + arguments: (arguments))))))""" + ), ) def test_multibyte_characters(self): parser = Parser() parser.set_language(JAVASCRIPT) source_code = bytes("'😎' && '🐍'", "utf8") tree = parser.parse(source_code) root_node = tree.root_node statement_node = root_node.children[0] binary_node = statement_node.children[0] snake_node = binary_node.children[2] self.assertEqual(binary_node.type, "binary_expression") self.assertEqual(snake_node.type, "string") self.assertEqual( - source_code[snake_node.start_byte:snake_node.end_byte].decode('utf8'), - "'🐍'" + source_code[snake_node.start_byte : snake_node.end_byte].decode( + "utf8" + ), + "'🐍'", ) def test_node_child_by_field_id(self): parser = Parser() parser.set_language(PYTHON) tree = parser.parse(b"def foo():\n bar()") root_node = tree.root_node fn_node = tree.root_node.children[0] - self.assertEqual(PYTHON.field_id_for_name('nameasdf'), None) - name_field = PYTHON.field_id_for_name('name') - alias_field = PYTHON.field_id_for_name('alias') + self.assertEqual(PYTHON.field_id_for_name("nameasdf"), None) + name_field = PYTHON.field_id_for_name("name") + alias_field = PYTHON.field_id_for_name("alias") self.assertIsInstance(alias_field, int) self.assertIsInstance(name_field, int) - self.assertRaises(TypeError, root_node.child_by_field_id, '') + self.assertRaises(TypeError, root_node.child_by_field_id, "") self.assertEqual(root_node.child_by_field_id(alias_field), None) self.assertEqual(root_node.child_by_field_id(name_field), None) self.assertEqual(fn_node.child_by_field_id(alias_field), None) self.assertEqual( - fn_node.child_by_field_id(name_field).type, - 'identifier' + fn_node.child_by_field_id(name_field).type, "identifier" ) self.assertRaises(TypeError, root_node.child_by_field_name, True) self.assertRaises(TypeError, root_node.child_by_field_name, 1) self.assertEqual( - fn_node.child_by_field_name('name').type, - 'identifier' + fn_node.child_by_field_name("name").type, "identifier" ) - self.assertEqual(fn_node.child_by_field_name('asdfasdfname'), None) + self.assertEqual(fn_node.child_by_field_name("asdfasdfname"), None) def test_node_children(self): parser = Parser() parser.set_language(PYTHON) tree = parser.parse(b"def foo():\n bar()") root_node = tree.root_node self.assertEqual(root_node.type, "module") self.assertEqual(root_node.start_byte, 0) self.assertEqual(root_node.end_byte, 18) self.assertEqual(root_node.start_point, (0, 0)) self.assertEqual(root_node.end_point, (1, 7)) # List object is reused self.assertIs(root_node.children, root_node.children) fn_node = root_node.children[0] self.assertEqual(fn_node.type, "function_definition") self.assertEqual(fn_node.start_byte, 0) self.assertEqual(fn_node.end_byte, 18) self.assertEqual(fn_node.start_point, (0, 0)) self.assertEqual(fn_node.end_point, (1, 7)) def_node = fn_node.children[0] self.assertEqual(def_node.type, "def") self.assertEqual(def_node.is_named, False) id_node = fn_node.children[1] self.assertEqual(id_node.type, "identifier") self.assertEqual(id_node.is_named, True) self.assertEqual(len(id_node.children), 0) params_node = fn_node.children[2] self.assertEqual(params_node.type, "parameters") self.assertEqual(params_node.is_named, True) colon_node = fn_node.children[3] self.assertEqual(colon_node.type, ":") self.assertEqual(colon_node.is_named, False) statement_node = fn_node.children[4] self.assertEqual(statement_node.type, "block") self.assertEqual(statement_node.is_named, True) def test_tree_walk(self): parser = Parser() parser.set_language(PYTHON) tree = parser.parse(b"def foo():\n bar()") cursor = tree.walk() # Node always returns the same instance self.assertIs(cursor.node, cursor.node) self.assertEqual(cursor.node.type, "module") self.assertEqual(cursor.node.start_byte, 0) self.assertEqual(cursor.node.end_byte, 18) self.assertEqual(cursor.node.start_point, (0, 0)) self.assertEqual(cursor.node.end_point, (1, 7)) self.assertTrue(cursor.goto_first_child()) self.assertEqual(cursor.node.type, "function_definition") self.assertEqual(cursor.node.start_byte, 0) self.assertEqual(cursor.node.end_byte, 18) self.assertEqual(cursor.node.start_point, (0, 0)) self.assertEqual(cursor.node.end_point, (1, 7)) self.assertTrue(cursor.goto_first_child()) self.assertEqual(cursor.node.type, "def") self.assertEqual(cursor.node.is_named, False) self.assertEqual(cursor.node.sexp(), '("def")') def_node = cursor.node # Node remains cached after a failure to move self.assertFalse(cursor.goto_first_child()) self.assertIs(cursor.node, def_node) self.assertTrue(cursor.goto_next_sibling()) self.assertEqual(cursor.node.type, "identifier") self.assertEqual(cursor.node.is_named, True) self.assertFalse(cursor.goto_first_child()) self.assertTrue(cursor.goto_next_sibling()) self.assertEqual(cursor.node.type, "parameters") self.assertEqual(cursor.node.is_named, True) def test_edit(self): parser = Parser() parser.set_language(PYTHON) tree = parser.parse(b"def foo():\n bar()") edit_offset = len(b"def foo(") tree.edit( - start_byte = edit_offset, - old_end_byte = edit_offset, - new_end_byte = edit_offset + 2, - start_point = (0, edit_offset), - old_end_point = (0, edit_offset), - new_end_point = (0, edit_offset + 2), + start_byte=edit_offset, + old_end_byte=edit_offset, + new_end_byte=edit_offset + 2, + start_point=(0, edit_offset), + old_end_point=(0, edit_offset), + new_end_point=(0, edit_offset + 2), ) fn_node = tree.root_node.children[0] - self.assertEqual(fn_node.type, 'function_definition') + self.assertEqual(fn_node.type, "function_definition") self.assertTrue(fn_node.has_changes) self.assertFalse(fn_node.children[0].has_changes) self.assertFalse(fn_node.children[1].has_changes) self.assertFalse(fn_node.children[3].has_changes) params_node = fn_node.children[2] - self.assertEqual(params_node.type, 'parameters') + self.assertEqual(params_node.type, "parameters") self.assertTrue(params_node.has_changes) self.assertEqual(params_node.start_point, (0, edit_offset - 1)) self.assertEqual(params_node.end_point, (0, edit_offset + 3)) new_tree = parser.parse(b"def foo(ab):\n bar()", tree) self.assertEqual( new_tree.root_node.sexp(), - "(module (function_definition " - "name: (identifier) " - "parameters: (parameters (identifier)) " - "body: (block " - "(expression_statement (call " - "function: (identifier) " - "arguments: (argument_list))))))" + _collapse_ws( + """(module (function_definition + name: (identifier) + parameters: (parameters (identifier)) + body: (block + (expression_statement (call + function: (identifier) + arguments: (argument_list))))))""" + ), ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..40c64c1 --- /dev/null +++ b/tox.ini @@ -0,0 +1,20 @@ +[tox] +envlist = lint, py + +[testenv:py] +deps = pytest +commands = pytest {posargs:tests} + +[testenv:lint] +deps = + black + flake8 + pylint +commands = + black -l 79 tree_sitter/__init__.py setup.py tests/test_tree_sitter.py + flake8 tree_sitter/__init__.py setup.py tests/test_tree_sitter.py + pylint tree_sitter/__init__.py setup.py tests/test_tree_sitter.py + +[flake8] +exclude = node_modules +ignore = E203,W503,F401 diff --git a/tree_sitter/__init__.py b/tree_sitter/__init__.py index 878a3b6..f006542 100644 --- a/tree_sitter/__init__.py +++ b/tree_sitter/__init__.py @@ -1,83 +1,96 @@ -from types import MethodType -from ctypes import cdll, c_void_p +"""Python bindings for tree-sitter.""" + +import platform +from ctypes import c_void_p +from ctypes import cdll from ctypes.util import find_library from distutils.ccompiler import new_compiler +from os import path from tempfile import TemporaryDirectory -from tree_sitter_binding import Parser, Tree, language_field_id_for_name -import os.path as path -import platform + +# pylint: disable=no-name-in-module,import-error +from tree_sitter.binding import _language_field_id_for_name +from tree_sitter.binding import Node +from tree_sitter.binding import Parser +from tree_sitter.binding import Tree +from tree_sitter.binding import TreeCursor class Language: + """A tree-sitter language""" + + @staticmethod def build_library(output_path, repo_paths): """ Build a dynamic library at the given path, based on the parser repositories at the given paths. Returns `True` if the dynamic library was compiled and `False` if the library already existed and was modified more recently than any of the source files. """ - output_mtime = 0 - if path.exists(output_path): - output_mtime = path.getmtime(output_path) + output_mtime = ( + path.getmtime(output_path) if path.exists(output_path) else 0 + ) - if len(repo_paths) == 0: - raise ValueError('Must provide at least one language folder') + if not repo_paths: + raise ValueError("Must provide at least one language folder") cpp = False source_paths = [] - source_mtimes = [path.getmtime(__file__)] for repo_path in repo_paths: - src_path = path.join(repo_path, 'src') + src_path = path.join(repo_path, "src") source_paths.append(path.join(src_path, "parser.c")) - source_mtimes.append(path.getmtime(source_paths[-1])) if path.exists(path.join(src_path, "scanner.cc")): cpp = True source_paths.append(path.join(src_path, "scanner.cc")) - source_mtimes.append(path.getmtime(source_paths[-1])) elif path.exists(path.join(src_path, "scanner.c")): source_paths.append(path.join(src_path, "scanner.c")) - source_mtimes.append(path.getmtime(source_paths[-1])) + source_mtimes = [path.getmtime(__file__)] + [ + path.getmtime(path_) for path_ in source_paths + ] compiler = new_compiler() if cpp: - if find_library('c++'): - compiler.add_library('c++') - elif find_library('stdc++'): - compiler.add_library('stdc++') + if find_library("c++"): + compiler.add_library("c++") + elif find_library("stdc++"): + compiler.add_library("stdc++") - if max(source_mtimes) > output_mtime: - with TemporaryDirectory(suffix = 'tree_sitter_language') as dir: - object_paths = [] - for source_path in source_paths: - if platform.system() == 'Windows': - flags = None - else: - flags = ['-fPIC'] - if source_path.endswith('.c'): - flags.append('-std=c99') - object_paths.append(compiler.compile( - [source_path], - output_dir = dir, - include_dirs = [path.dirname(source_path)], - extra_preargs = flags - )[0]) - compiler.link_shared_object(object_paths, output_path) - return True - else: + if max(source_mtimes) <= output_mtime: return False + with TemporaryDirectory(suffix="tree_sitter_language") as out_dir: + object_paths = [] + for source_path in source_paths: + if platform.system() == "Windows": + flags = None + else: + flags = ["-fPIC"] + if source_path.endswith(".c"): + flags.append("-std=c99") + object_paths.append( + compiler.compile( + [source_path], + output_dir=out_dir, + include_dirs=[path.dirname(source_path)], + extra_preargs=flags, + )[0] + ) + compiler.link_shared_object(object_paths, output_path) + return True + def __init__(self, library_path, name): """ Load the language with the given name from the dynamic library at the given path. """ self.name = name self.lib = cdll.LoadLibrary(library_path) language_function = getattr(self.lib, "tree_sitter_%s" % name) language_function.restype = c_void_p self.language_id = language_function() def field_id_for_name(self, name): - return language_field_id_for_name(self.language_id, name) + """Return the field id for a field name.""" + return _language_field_id_for_name(self.language_id, name) diff --git a/tree_sitter/binding.c b/tree_sitter/binding.c index 682ba29..fb6e50d 100644 --- a/tree_sitter/binding.c +++ b/tree_sitter/binding.c @@ -1,582 +1,600 @@ #include "Python.h" #include "tree_sitter/api.h" // Types typedef struct { PyObject_HEAD TSNode node; PyObject *children; } Node; typedef struct { PyObject_HEAD TSTree *tree; } Tree; typedef struct { PyObject_HEAD TSParser *parser; } Parser; typedef struct { PyObject_HEAD TSTreeCursor cursor; PyObject *node; } TreeCursor; static TSTreeCursor default_cursor = {0}; // Point static PyObject *point_new(TSPoint point) { PyObject *row = PyLong_FromSize_t((size_t)point.row); PyObject *column = PyLong_FromSize_t((size_t)point.column); if (!row || !column) { Py_XDECREF(row); Py_XDECREF(column); return NULL; } return PyTuple_Pack(2, row, column); } // Node static PyObject *node_new_internal(TSNode node); static PyObject *tree_cursor_new_internal(TSNode node); static void node_dealloc(Node *self) { Py_XDECREF(self->children); Py_TYPE(self)->tp_free(self); } static PyObject *node_repr(Node *self) { const char *type = ts_node_type(self->node); TSPoint start_point = ts_node_start_point(self->node); TSPoint end_point = ts_node_end_point(self->node); const char *format_string = ts_node_is_named(self->node) ? "" : ""; return PyUnicode_FromFormat( format_string, type, start_point.row, start_point.column, end_point.row, end_point.column ); } static PyObject *node_sexp(Node *self, PyObject *args) { char *string = ts_node_string(self->node); PyObject *result = PyUnicode_FromString(string); free(string); return result; } static PyObject *node_walk(Node *self, PyObject *args) { return tree_cursor_new_internal(self->node); } static PyObject *node_chield_by_field_id(Node *self, PyObject *args) { TSFieldId field_id; if (!PyArg_ParseTuple(args, "H", &field_id)) { return NULL; } TSNode child = ts_node_child_by_field_id(self->node, field_id); if (ts_node_is_null(child)) { Py_RETURN_NONE; } return node_new_internal(child); } static PyObject *node_chield_by_field_name(Node *self, PyObject *args) { char *name; int length; if (!PyArg_ParseTuple(args, "s#", &name, &length)) { return NULL; } TSNode child = ts_node_child_by_field_name(self->node, name, length); if (ts_node_is_null(child)) { Py_RETURN_NONE; } return node_new_internal(child); } static PyObject *node_get_type(Node *self, void *payload) { return PyUnicode_FromString(ts_node_type(self->node)); } static PyObject *node_get_is_named(Node *self, void *payload) { return PyBool_FromLong(ts_node_is_named(self->node)); } static PyObject *node_get_has_changes(Node *self, void *payload) { return PyBool_FromLong(ts_node_has_changes(self->node)); } static PyObject *node_get_has_error(Node *self, void *payload) { return PyBool_FromLong(ts_node_has_error(self->node)); } static PyObject *node_get_start_byte(Node *self, void *payload) { return PyLong_FromSize_t((size_t)ts_node_start_byte(self->node)); } static PyObject *node_get_end_byte(Node *self, void *payload) { return PyLong_FromSize_t((size_t)ts_node_end_byte(self->node)); } static PyObject *node_get_start_point(Node *self, void *payload) { return point_new(ts_node_start_point(self->node)); } static PyObject *node_get_end_point(Node *self, void *payload) { return point_new(ts_node_end_point(self->node)); } static PyObject *node_get_children(Node *self, void *payload) { if (self->children) { Py_INCREF(self->children); return self->children; } long length = (long)ts_node_child_count(self->node); PyObject *result = PyList_New(length); if (length > 0) { ts_tree_cursor_reset(&default_cursor, self->node); ts_tree_cursor_goto_first_child(&default_cursor); int i = 0; do { TSNode child = ts_tree_cursor_current_node(&default_cursor); PyList_SetItem(result, i, node_new_internal(child)); i++; } while (ts_tree_cursor_goto_next_sibling(&default_cursor)); } Py_INCREF(result); self->children = result; return result; } static PyMethodDef node_methods[] = { { .ml_name = "walk", .ml_meth = (PyCFunction)node_walk, .ml_flags = METH_NOARGS, - .ml_doc = "Get a tree cursor for walking the tree starting at this node", + .ml_doc = "walk()\n--\n\n\ + Get a tree cursor for walking the tree starting at this node.", }, { .ml_name = "sexp", .ml_meth = (PyCFunction)node_sexp, .ml_flags = METH_NOARGS, - .ml_doc = "Get an S-expression representing the name", + .ml_doc = "sexp()\n--\n\n\ + Get an S-expression representing the node.", }, { .ml_name = "child_by_field_id", .ml_meth = (PyCFunction)node_chield_by_field_id, .ml_flags = METH_VARARGS, - .ml_doc = "Get child for the given field id.", + .ml_doc = "child_by_field_id(id)\n--\n\n\ + Get child for the given field id.", }, { .ml_name = "child_by_field_name", .ml_meth = (PyCFunction)node_chield_by_field_name, .ml_flags = METH_VARARGS, - .ml_doc = "Get child for the given field name.", + .ml_doc = "child_by_field_name(name)\n--\n\n\ + Get child for the given field name.", }, {NULL}, }; static PyGetSetDef node_accessors[] = { {"type", (getter)node_get_type, NULL, "The node's type", NULL}, {"is_named", (getter)node_get_is_named, NULL, "Is this a named node", NULL}, {"has_changes", (getter)node_get_has_changes, NULL, "Does this node have text changes since it was parsed", NULL}, {"has_error", (getter)node_get_has_error, NULL, "Does this node contain any errors", NULL}, {"start_byte", (getter)node_get_start_byte, NULL, "The node's start byte", NULL}, {"end_byte", (getter)node_get_end_byte, NULL, "The node's end byte", NULL}, {"start_point", (getter)node_get_start_point, NULL, "The node's start point", NULL}, {"end_point", (getter)node_get_end_point, NULL, "The node's end point", NULL}, {"children", (getter)node_get_children, NULL, "The node's children", NULL}, {NULL} }; static PyTypeObject node_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "tree_sitter.Node", .tp_doc = "A syntax node", .tp_basicsize = sizeof(Node), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_dealloc = (destructor)node_dealloc, .tp_repr = (reprfunc)node_repr, .tp_methods = node_methods, .tp_getset = node_accessors, }; static PyObject *node_new_internal(TSNode node) { Node *self = (Node *)node_type.tp_alloc(&node_type, 0); if (self != NULL) { self->node = node; self->children = NULL; } return (PyObject *)self; } // Tree static void tree_dealloc(Tree *self) { ts_tree_delete(self->tree); Py_TYPE(self)->tp_free((PyObject *)self); } static PyObject *tree_get_root_node(Tree *self, void *payload) { return node_new_internal(ts_tree_root_node(self->tree)); } static PyObject *tree_walk(Tree *self, PyObject *args) { return tree_cursor_new_internal(ts_tree_root_node(self->tree)); } static PyObject *tree_edit(Tree *self, PyObject *args, PyObject *kwargs) { unsigned start_byte, start_row, start_column; unsigned old_end_byte, old_end_row, old_end_column; unsigned new_end_byte, new_end_row, new_end_column; char *keywords[] = { "start_byte", "old_end_byte", "new_end_byte", "start_point", "old_end_point", "new_end_point", NULL, }; int ok = PyArg_ParseTupleAndKeywords( args, kwargs, "III(II)(II)(II)", keywords, &start_byte, &old_end_byte, &new_end_byte, &start_row, &start_column, &old_end_row, &old_end_column, &new_end_row, &new_end_column ); if (ok) { TSInputEdit edit = { .start_byte = start_byte, .old_end_byte = old_end_byte, .new_end_byte = new_end_byte, .start_point = {start_row, start_column}, .old_end_point = {old_end_row, old_end_column}, .new_end_point = {new_end_row, new_end_column}, }; ts_tree_edit(self->tree, &edit); } Py_RETURN_NONE; } static PyMethodDef tree_methods[] = { { .ml_name = "walk", .ml_meth = (PyCFunction)tree_walk, .ml_flags = METH_NOARGS, - .ml_doc = "Get a tree cursor for walking this tree", + .ml_doc = "walk()\n--\n\n\ + Get a tree cursor for walking this tree.", }, { .ml_name = "edit", .ml_meth = (PyCFunction)tree_edit, .ml_flags = METH_KEYWORDS|METH_VARARGS, - .ml_doc = "Edit a syntax tree", + .ml_doc = "edit(start_byte, old_end_byte, new_end_byte,\ + start_point, old_end_point, new_end_point)\n--\n\n\ + Edit the syntax tree.", }, {NULL}, }; static PyGetSetDef tree_accessors[] = { - {"root_node", (getter)tree_get_root_node, NULL, "root node", NULL}, + {"root_node", (getter)tree_get_root_node, NULL, "The root node of this tree.", NULL}, {NULL} }; static PyTypeObject tree_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "tree_sitter.Tree", .tp_doc = "A Syntax Tree", .tp_basicsize = sizeof(Tree), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_dealloc = (destructor)tree_dealloc, .tp_methods = tree_methods, .tp_getset = tree_accessors, }; static PyObject *tree_new_internal(TSTree *tree) { Tree *self = (Tree *)tree_type.tp_alloc(&tree_type, 0); if (self != NULL) self->tree = tree; return (PyObject *)self; } // TreeCursor static void tree_cursor_dealloc(TreeCursor *self) { ts_tree_cursor_delete(&self->cursor); Py_XDECREF(self->node); Py_TYPE(self)->tp_free((PyObject *)self); } static PyObject *tree_cursor_get_node(TreeCursor *self, void *payload) { if (!self->node) { self->node = node_new_internal(ts_tree_cursor_current_node(&self->cursor)); } Py_INCREF(self->node); return self->node; } static PyObject *tree_cursor_goto_parent(TreeCursor *self, PyObject *args) { bool result = ts_tree_cursor_goto_parent(&self->cursor); if (result) { Py_XDECREF(self->node); self->node = NULL; } return PyBool_FromLong(result); } static PyObject *tree_cursor_goto_first_child(TreeCursor *self, PyObject *args) { bool result = ts_tree_cursor_goto_first_child(&self->cursor); if (result) { Py_XDECREF(self->node); self->node = NULL; } return PyBool_FromLong(result); } static PyObject *tree_cursor_goto_next_sibling(TreeCursor *self, PyObject *args) { bool result = ts_tree_cursor_goto_next_sibling(&self->cursor); if (result) { Py_XDECREF(self->node); self->node = NULL; } return PyBool_FromLong(result); } static PyMethodDef tree_cursor_methods[] = { { .ml_name = "goto_parent", .ml_meth = (PyCFunction)tree_cursor_goto_parent, .ml_flags = METH_NOARGS, - .ml_doc = "If the current node is not the root, move to its parent and return True. Otherwise, return false.", + .ml_doc = "goto_parent()\n--\n\n\ + Go to parent.\n\n\ + If the current node is not the root, move to its parent and\n\ + return True. Otherwise, return False.", }, { .ml_name = "goto_first_child", .ml_meth = (PyCFunction)tree_cursor_goto_first_child, .ml_flags = METH_NOARGS, - .ml_doc = "If the current node has children, move to the first child and return True. Otherwise, return false.", + .ml_doc = "goto_first_child()\n--\n\n\ + Go to first child.\n\n\ + If the current node has children, move to the first child and\n\ + return True. Otherwise, return False.", }, { .ml_name = "goto_next_sibling", .ml_meth = (PyCFunction)tree_cursor_goto_next_sibling, .ml_flags = METH_NOARGS, - .ml_doc = "If the current node has a next sibling, move to the next sibling and return True. Otherwise, return false.", + .ml_doc = "goto_next_sibling()\n--\n\n\ + Go to next sibling.\n\n\ + If the current node has a next sibling, move to the next sibling\n\ + and return True. Otherwise, return False.", }, {NULL}, }; static PyGetSetDef tree_cursor_accessors[] = { - {"node", (getter)tree_cursor_get_node, NULL, "node", NULL}, + {"node", (getter)tree_cursor_get_node, NULL, "The current node.", NULL}, {NULL}, }; static PyTypeObject tree_cursor_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "tree_sitter.TreeCursor", - .tp_doc = "A syntax tree cursor", + .tp_doc = "A syntax tree cursor.", .tp_basicsize = sizeof(TreeCursor), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_dealloc = (destructor)tree_cursor_dealloc, .tp_methods = tree_cursor_methods, .tp_getset = tree_cursor_accessors, }; static PyObject *tree_cursor_new_internal(TSNode node) { TreeCursor *cursor = (TreeCursor *)tree_cursor_type.tp_alloc(&tree_cursor_type, 0); if (cursor != NULL) cursor->cursor = ts_tree_cursor_new(node); return (PyObject *)cursor; } // Parser static PyObject *parser_new( PyTypeObject *type, PyObject *args, PyObject *kwds ) { Parser *self = (Parser *)type->tp_alloc(type, 0); if (self != NULL) self->parser = ts_parser_new(); return (PyObject *)self; } static void parser_dealloc(Parser *self) { ts_parser_delete(self->parser); Py_TYPE(self)->tp_free((PyObject *)self); } static PyObject *parser_parse(Parser *self, PyObject *args) { PyObject *source_code = NULL; PyObject *old_tree_arg = NULL; if (!PyArg_UnpackTuple(args, "ref", 1, 2, &source_code, &old_tree_arg)) { return NULL; } if (!PyBytes_Check(source_code)) { PyErr_SetString(PyExc_TypeError, "First argument to parse must be bytes"); return NULL; } const TSTree *old_tree = NULL; if (old_tree_arg) { if (!PyObject_IsInstance(old_tree_arg, (PyObject *)&tree_type)) { PyErr_SetString(PyExc_TypeError, "Second argument to parse must be a Tree"); return NULL; } old_tree = ((Tree *)old_tree_arg)->tree; } size_t length = PyBytes_Size(source_code); char *source_bytes = PyBytes_AsString(source_code); TSTree *new_tree = ts_parser_parse_string(self->parser, old_tree, source_bytes, length); if (!new_tree) { PyErr_SetString(PyExc_ValueError, "Parsing failed"); return NULL; } return tree_new_internal(new_tree); } static PyObject *parser_set_language(Parser *self, PyObject *arg) { PyObject *language_id = PyObject_GetAttrString(arg, "language_id"); if (!language_id) { PyErr_SetString(PyExc_TypeError, "Argument to set_language must be a Language"); return NULL; } if (!PyLong_Check(language_id)) { PyErr_SetString(PyExc_TypeError, "Language ID must be an integer"); return NULL; } TSLanguage *language = (TSLanguage *)PyLong_AsLong(language_id); if (!language) { PyErr_SetString(PyExc_ValueError, "Language ID must not be null"); return NULL; } unsigned version = ts_language_version(language); if (version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION || TREE_SITTER_LANGUAGE_VERSION < version) { return PyErr_Format( PyExc_ValueError, "Incompatible Language version %u. Must not be between %u and %u", version, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, TREE_SITTER_LANGUAGE_VERSION ); } ts_parser_set_language(self->parser, language); Py_RETURN_NONE; } static PyMethodDef parser_methods[] = { { .ml_name = "parse", .ml_meth = (PyCFunction)parser_parse, .ml_flags = METH_VARARGS, - .ml_doc = "Parse source code, creating a syntax tree", + .ml_doc = "parse(bytes, old_tree=None)\n--\n\n\ + Parse source code, creating a syntax tree.", }, { .ml_name = "set_language", .ml_meth = (PyCFunction)parser_set_language, .ml_flags = METH_O, - .ml_doc = "Set the parser language", + .ml_doc = "set_language(language)\n--\n\n\ + Set the parser language.", }, {NULL}, }; static PyTypeObject parser_type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "tree_sitter.Parser", .tp_doc = "A Parser", .tp_basicsize = sizeof(Parser), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_new = parser_new, .tp_dealloc = (destructor)parser_dealloc, .tp_methods = parser_methods, }; // Module static PyObject *language_field_id_for_name(Node *self, PyObject *args) { TSLanguage *language; char *field_name; int length; if (!PyArg_ParseTuple(args, "ls#", &language, &field_name, &length)) { return NULL; } TSFieldId field_id = ts_language_field_id_for_name(language, field_name, length); if (field_id == 0) { Py_RETURN_NONE; } return PyLong_FromSize_t((size_t)field_id); } static PyMethodDef module_methods[] = { { - .ml_name = "language_field_id_for_name", + .ml_name = "_language_field_id_for_name", .ml_meth = (PyCFunction)language_field_id_for_name, .ml_flags = METH_VARARGS, - .ml_doc = "", + .ml_doc = "(internal)", }, {NULL}, }; static struct PyModuleDef module_definition = { .m_base = PyModuleDef_HEAD_INIT, - .m_name = "tree_sitter", + .m_name = "binding", .m_doc = NULL, .m_size = -1, .m_methods = module_methods, }; -PyMODINIT_FUNC PyInit_tree_sitter_binding(void) { +PyMODINIT_FUNC PyInit_binding(void) { PyObject *module = PyModule_Create(&module_definition); if (module == NULL) return NULL; if (PyType_Ready(&parser_type) < 0) return NULL; Py_INCREF(&parser_type); PyModule_AddObject(module, "Parser", (PyObject *)&parser_type); if (PyType_Ready(&tree_type) < 0) return NULL; Py_INCREF(&tree_type); PyModule_AddObject(module, "Tree", (PyObject *)&tree_type); if (PyType_Ready(&node_type) < 0) return NULL; Py_INCREF(&node_type); PyModule_AddObject(module, "Node", (PyObject *)&node_type); if (PyType_Ready(&tree_cursor_type) < 0) return NULL; Py_INCREF(&tree_cursor_type); PyModule_AddObject(module, "TreeCursor", (PyObject *)&tree_cursor_type); return module; }