From fd8ad36fe5ea3b9b12e80879b7434b8bc67b53db Mon Sep 17 00:00:00 2001
From: Yuan Fu <casouri@gmail.com>
Date: Wed, 21 Jul 2021 22:43:07 -0400
Subject: [PATCH] checkpoint 3

- change_hook -> directly in edit functions
- add a need_reparse field in Lisp_TS_Parser
- more node api
- tests
---
 src/insdel.c                  |  43 ++++--
 src/tree_sitter.c             | 274 ++++++++++++++++++++++++++++------
 src/tree_sitter.h             |  18 ++-
 test/src/tree-sitter-tests.el | 106 +++++++++++++
 4 files changed, 377 insertions(+), 64 deletions(-)
 create mode 100644 test/src/tree-sitter-tests.el

diff --git a/src/insdel.c b/src/insdel.c
index 3c1e13d38b..b313c50cda 100644
--- a/src/insdel.c
+++ b/src/insdel.c
@@ -947,6 +947,10 @@ insert_1_both (const char *string,
   adjust_point (nchars, nbytes);
 
   check_markers ();
+
+#ifdef HAVE_TREE_SITTER
+      ts_record_change (PT_BYTE - nbytes, PT_BYTE - nbytes, PT_BYTE);
+#endif
 }
 
 /* Insert the part of the text of STRING, a Lisp object assumed to be
@@ -1078,6 +1082,10 @@ insert_from_string_1 (Lisp_Object string, ptrdiff_t pos, ptrdiff_t pos_byte,
   adjust_point (nchars, outgoing_nbytes);
 
   check_markers ();
+
+#ifdef HAVE_TREE_SITTER
+  ts_record_change (PT_BYTE - nbytes, PT_BYTE - nbytes, PT_BYTE);
+#endif
 }
 
 /* Insert a sequence of NCHARS chars which occupy NBYTES bytes
@@ -1145,6 +1153,10 @@ insert_from_gap (ptrdiff_t nchars, ptrdiff_t nbytes, bool text_at_gap_tail)
     adjust_point (nchars, nbytes);
 
   check_markers ();
+
+#ifdef HAVE_TREE_SITTER
+  ts_record_change (PT_BYTE - nbytes, PT_BYTE - nbytes, nbytes);
+#endif
 }
 
 /* Insert text from BUF, NCHARS characters starting at CHARPOS, into the
@@ -1292,6 +1304,11 @@ insert_from_buffer_1 (struct buffer *buf,
   graft_intervals_into_buffer (intervals, PT, nchars, current_buffer, inherit);
 
   adjust_point (nchars, outgoing_nbytes);
+
+#ifdef HAVE_TREE_SITTER
+  ts_record_change (PT_BYTE - outgoing_nbytes,
+		    PT_BYTE - outgoing_nbytes, PT_BYTE);
+#endif
 }
 
 /* Record undo information and adjust markers and position keepers for
@@ -1556,6 +1573,11 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new,
   if (adjust_match_data)
     update_search_regs (from, to, from + SCHARS (new));
 
+
+#ifdef HAVE_TREE_SITTER
+  ts_record_change (from_byte, to_byte, GPT_BYTE);
+#endif
+
   signal_after_change (from, nchars_del, GPT - from);
   update_compositions (from, GPT, CHECK_BORDER);
 }
@@ -1683,6 +1705,11 @@ replace_range_2 (ptrdiff_t from, ptrdiff_t from_byte,
 
   modiff_incr (&MODIFF);
   CHARS_MODIFF = MODIFF;
+
+#ifdef HAVE_TREE_SITTER
+  ts_record_change (from_byte, to_byte, from_byte + insbytes);
+#endif
+
 }
 
 /* Delete characters in current buffer
@@ -1893,6 +1920,10 @@ del_range_2 (ptrdiff_t from, ptrdiff_t from_byte,
 
   evaporate_overlays (from);
 
+#ifdef HAVE_TREE_SITTER
+  ts_record_change (from_byte, to_byte, from_byte);
+#endif
+
   return deletion;
 }
 
@@ -2156,11 +2187,6 @@ signal_before_change (ptrdiff_t start_int, ptrdiff_t end_int,
       run_hook (Qfirst_change_hook);
     }
 
-#ifdef HAVE_TREE_SITTER
-  /* FIXME: Is this the best place?  */
-  ts_before_change (start_int, end_int);
-#endif
-
   /* Now run the before-change-functions if any.  */
   if (!NILP (Vbefore_change_functions))
     {
@@ -2214,13 +2240,6 @@ signal_after_change (ptrdiff_t charpos, ptrdiff_t lendel, ptrdiff_t lenins)
   if (inhibit_modification_hooks)
     return;
 
-#ifdef HAVE_TREE_SITTER
-  /* We disrespect combine-after-change, because if we don't record
-     this change, the information that we need (the end byte position
-     of the change) will be lost.  */
-  ts_after_change (charpos, lendel, lenins);
-#endif
-
   /* If we are deferring calls to the after-change functions
      and there are no before-change functions,
      just record the args that we were going to use.  */
diff --git a/src/tree_sitter.c b/src/tree_sitter.c
index 7d1225161c..a6a8912c84 100644
--- a/src/tree_sitter.c
+++ b/src/tree_sitter.c
@@ -32,49 +32,52 @@ Copyright (C) 2021 Free Software Foundation, Inc.
 #include "coding.h"
 #include "tree_sitter.h"
 
-/* parser.h defines a macro ADVANCE that conflicts with alloc.c.   */
+/* parser.h defines a macro ADVANCE that conflicts with alloc.c.  */
 #include <tree_sitter/parser.h>
 
-/* Record the byte position of the end of the (to-be) changed text.
-We have to record it now, because by the time we get to after-change
-hook, the _byte_ position of the end is lost.  */
-void
-ts_before_change (ptrdiff_t start_int, ptrdiff_t end_int)
+DEFUN ("tree-sitter-parser-p",
+       Ftree_sitter_parser_p, Stree_sitter_parser_p, 1, 1, 0,
+       doc: /* Return t if OBJECT is a tree-sitter parser.  */)
+  (Lisp_Object object)
 {
-  /* Iterate through each parser in 'tree-sitter-parser-list' and
-     record the byte position.  There could be better ways to record
-     it than storing the same position in every parser, but this is
-     the most fool-proof way, and I expect a buffer to have only one
-     parser most of the time anyway. */
-  ptrdiff_t beg_byte = CHAR_TO_BYTE (start_int);
-  ptrdiff_t old_end_byte = CHAR_TO_BYTE (end_int);
-  Lisp_Object parser_list = Fsymbol_value (Qtree_sitter_parser_list);
-  while (!NILP (parser_list))
-    {
-      Lisp_Object lisp_parser = Fcar (parser_list);
-      XTS_PARSER (lisp_parser)->edit.start_byte = beg_byte;
-      XTS_PARSER (lisp_parser)->edit.old_end_byte = old_end_byte;
-      parser_list = Fcdr (parser_list);
-    }
+  if (TS_PARSERP (object))
+    return Qt;
+  else
+    return Qnil;
+}
+
+DEFUN ("tree-sitter-node-p",
+       Ftree_sitter_node_p, Stree_sitter_node_p, 1, 1, 0,
+       doc: /* Return t if OBJECT is a tree-sitter node.  */)
+  (Lisp_Object object)
+{
+  if (TS_NODEP (object))
+    return Qt;
+  else
+    return Qnil;
 }
 
 /* Update each parser's tree after the user made an edit.  This
 function does not parse the buffer and only updates the tree. (So it
 should be very fast.)  */
 void
-ts_after_change (ptrdiff_t charpos, ptrdiff_t lendel, ptrdiff_t lenins)
+ts_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
+		  ptrdiff_t new_end_byte)
 {
-  ptrdiff_t new_end_byte = CHAR_TO_BYTE (charpos + lenins);
   Lisp_Object parser_list = Fsymbol_value (Qtree_sitter_parser_list);
+  TSPoint dummy_point = {0, 0};
+  TSInputEdit edit = {start_byte, old_end_byte, new_end_byte,
+		      dummy_point, dummy_point, dummy_point};
   while (!NILP (parser_list))
     {
       Lisp_Object lisp_parser = Fcar (parser_list);
       TSTree *tree = XTS_PARSER (lisp_parser)->tree;
-      XTS_PARSER (lisp_parser)->edit.new_end_byte = new_end_byte;
       if (tree != NULL)
-	  ts_tree_edit (tree, &XTS_PARSER (lisp_parser)->edit);
+	ts_tree_edit (tree, &edit);
+      XTS_PARSER (lisp_parser)->need_reparse = true;
       parser_list = Fcdr (parser_list);
     }
+
 }
 
 /* Parse the buffer.  We don't parse until we have to. When we have
@@ -82,11 +85,15 @@ ts_after_change (ptrdiff_t charpos, ptrdiff_t lendel, ptrdiff_t lenins)
 void
 ts_ensure_parsed (Lisp_Object parser)
 {
+  if (!XTS_PARSER (parser)->need_reparse)
+    return;
   TSParser *ts_parser = XTS_PARSER (parser)->parser;
   TSTree *tree = XTS_PARSER(parser)->tree;
   TSInput input = XTS_PARSER (parser)->input;
   TSTree *new_tree = ts_parser_parse(ts_parser, tree, input);
+  ts_tree_delete (tree);
   XTS_PARSER (parser)->tree = new_tree;
+  XTS_PARSER (parser)->need_reparse = false;
 }
 
 /* This is the read function provided to tree-sitter to read from a
@@ -96,33 +103,30 @@ ts_ensure_parsed (Lisp_Object parser)
 ts_read_buffer (void *buffer, uint32_t byte_index,
 		TSPoint position, uint32_t *bytes_read)
 {
-  if (! BUFFER_LIVE_P ((struct buffer *) buffer))
+  if (!BUFFER_LIVE_P ((struct buffer *) buffer))
     error ("BUFFER is not live");
 
   ptrdiff_t byte_pos = byte_index + 1;
 
-  // FIXME: Add some boundary checks?
-  /* I believe we can get away with only setting current-buffer
-     and not actually switching to it, like what we did in
-     'make_gap_1'.  */
-  struct buffer *old_buffer = current_buffer;
-  current_buffer = (struct buffer *) buffer;
-
-  /* Read one character.  */
+  /* Read one character.  Tree-sitter wants us to set bytes_read to 0
+     if it reads to the end of buffer.  It doesn't say what it wants
+     for the return value in that case, so we just give it an empty
+     string.  */
   char *beg;
   int len;
-  if (byte_pos >= Z_BYTE)
+  // TODO BUF_ZV_BYTE?
+  if (byte_pos >= BUF_Z_BYTE ((struct buffer *) buffer))
     {
       beg = "";
       len = 0;
     }
   else
     {
-      beg = (char *) BYTE_POS_ADDR (byte_pos);
+      beg = (char *) BUF_BYTE_ADDRESS (buffer, byte_pos);
       len = next_char_len(byte_pos);
     }
   *bytes_read = (uint32_t) len;
-  current_buffer = old_buffer;
+
   return beg;
 }
 
@@ -137,9 +141,7 @@ make_ts_parser (struct buffer *buffer, TSParser *parser, TSTree *tree)
   lisp_parser->tree = tree;
   TSInput input = {buffer, ts_read_buffer, TSInputEncodingUTF8};
   lisp_parser->input = input;
-  TSPoint dummy_point = {0, 0};
-  TSInputEdit edit = {0, 0, 0, dummy_point, dummy_point, dummy_point};
-  lisp_parser->edit = edit;
+  lisp_parser->need_reparse = true;
   return make_lisp_ptr (lisp_parser, Lisp_Vectorlike);
 }
 
@@ -192,6 +194,7 @@ DEFUN ("tree-sitter-parser-root-node",
        doc: /* Return the root node of PARSER.  */)
   (Lisp_Object parser)
 {
+  CHECK_TS_PARSER (parser);
   ts_ensure_parsed(parser);
   TSNode root_node = ts_tree_root_node (XTS_PARSER (parser)->tree);
   return make_ts_node (parser, root_node);
@@ -229,11 +232,29 @@ DEFUN ("tree-sitter-parse", Ftree_sitter_parse, Stree_sitter_parse,
   return lisp_node;
 }
 
+/* Below this point are uninteresting mechanical translations of
+   tree-sitter API.  */
+
+/* Node functions.  */
+
+DEFUN ("tree-sitter-node-type",
+       Ftree_sitter_node_type, Stree_sitter_node_type, 1, 1, 0,
+       doc: /* Return the NODE's type as a symbol.  */)
+  (Lisp_Object node)
+{
+  CHECK_TS_NODE (node);
+  TSNode ts_node = XTS_NODE (node)->node;
+  const char *type = ts_node_type(ts_node);
+  return intern_c_string (type);
+}
+
+
 DEFUN ("tree-sitter-node-string",
        Ftree_sitter_node_string, Stree_sitter_node_string, 1, 1, 0,
        doc: /* Return the string representation of NODE.  */)
   (Lisp_Object node)
 {
+  CHECK_TS_NODE (node);
   TSNode ts_node = XTS_NODE (node)->node;
   char *string = ts_node_string(ts_node);
   return make_string(string, strlen (string));
@@ -242,29 +263,125 @@ DEFUN ("tree-sitter-node-string",
 DEFUN ("tree-sitter-node-parent",
        Ftree_sitter_node_parent, Stree_sitter_node_parent, 1, 1, 0,
        doc: /* Return the immediate parent of NODE.
-Return nil if we couldn't find any.  */)
+Return nil if there isn't any.  */)
   (Lisp_Object node)
 {
+  CHECK_TS_NODE (node);
   TSNode ts_node = XTS_NODE (node)->node;
-  TSNode parent = ts_node_parent(ts_node);
+  TSNode parent = ts_node_parent (ts_node);
 
   if (ts_node_is_null(parent))
     return Qnil;
 
-  return make_ts_node(XTS_NODE (node)->parser, parent);
+  return make_ts_node (XTS_NODE (node)->parser, parent);
 }
 
 DEFUN ("tree-sitter-node-child",
-       Ftree_sitter_node_child, Stree_sitter_node_child, 2, 2, 0,
+       Ftree_sitter_node_child, Stree_sitter_node_child, 2, 3, 0,
        doc: /* Return the Nth child of NODE.
-Return nil if we couldn't find any.  */)
+Return nil if there isn't any.  If NAMED is non-nil, look for named
+child only.  NAMED defaults to nil.  */)
+  (Lisp_Object node, Lisp_Object n, Lisp_Object named)
+{
+  CHECK_TS_NODE (node);
+  CHECK_INTEGER (n);
+  EMACS_INT idx = XFIXNUM (n);
+  TSNode ts_node = XTS_NODE (node)->node;
+  TSNode child;
+  if (NILP (named))
+    child = ts_node_child (ts_node, (uint32_t) idx);
+  else
+    child = ts_node_named_child (ts_node, (uint32_t) idx);
+
+  if (ts_node_is_null(child))
+    return Qnil;
+
+  return make_ts_node(XTS_NODE (node)->parser, child);
+}
+
+DEFUN ("tree-sitter-node-check",
+       Ftree_sitter_node_check, Stree_sitter_node_check, 2, 2, 0,
+       doc: /* Return non-nil if NODE is in condition COND, nil otherwise.
+
+COND could be 'named, 'missing, 'extra, 'has-error.  Named nodes
+correspond to named rules in the grammar, whereas "anonymous" nodes
+correspond to string literals in the grammar.
+
+Missing nodes are inserted by the parser in order to recover from
+certain kinds of syntax errors, i.e., should be there but not there.
+
+Extra nodes represent things like comments, which are not required the
+grammar, but can appear anywhere.
+
+A node "has error" if itself is a syntax error or contains any syntax
+errors.  */)
+  (Lisp_Object node, Lisp_Object cond)
+{
+  CHECK_TS_NODE (node);
+  CHECK_SYMBOL (cond);
+  TSNode ts_node = XTS_NODE (node)->node;
+  bool result;
+  if (EQ (cond, Qnamed))
+    result = ts_node_is_named (ts_node);
+  else if (EQ (cond, Qmissing))
+    result = ts_node_is_missing (ts_node);
+  else if (EQ (cond, Qextra))
+    result = ts_node_is_extra (ts_node);
+  else if (EQ (cond, Qhas_error))
+    result = ts_node_has_error (ts_node);
+  else
+    signal_error ("Expecting one of four symbols, see docstring", cond);
+  return result ? Qt : Qnil;
+}
+
+DEFUN ("tree-sitter-node-field-name-for-child",
+       Ftree_sitter_node_field_name_for_child,
+       Stree_sitter_node_field_name_for_child, 2, 2, 0,
+       doc: /* Return the field name of the Nth child of NODE.
+Return nil if there isn't any child or no field is found.  */)
   (Lisp_Object node, Lisp_Object n)
 {
   CHECK_INTEGER (n);
   EMACS_INT idx = XFIXNUM (n);
   TSNode ts_node = XTS_NODE (node)->node;
-  // FIXME: Is this cast ok?
-  TSNode child = ts_node_child(ts_node, (uint32_t) idx);
+  const char *name
+    = ts_node_field_name_for_child (ts_node, (uint32_t) idx);
+
+  if (name == NULL)
+    return Qnil;
+
+  return make_string (name, strlen (name));
+}
+
+DEFUN ("tree-sitter-node-child-count",
+       Ftree_sitter_node_child_count,
+       Stree_sitter_node_child_count, 1, 2, 0,
+       doc: /* Return the number of children of NODE.
+If NAMED is non-nil, count named child only.  NAMED defaults to
+nil.  */)
+  (Lisp_Object node, Lisp_Object named)
+{
+  TSNode ts_node = XTS_NODE (node)->node;
+  uint32_t count;
+  if (NILP (named))
+    count = ts_node_child_count (ts_node);
+  else
+    count = ts_node_named_child_count (ts_node);
+  return make_fixnum (count);
+}
+
+DEFUN ("tree-sitter-node-child-by-field-name",
+       Ftree_sitter_node_child_by_field_name,
+       Stree_sitter_node_child_by_field_name, 2, 2, 0,
+       doc: /* Return the child of NODE with field name NAME.
+Return nil if there isn't any.  */)
+  (Lisp_Object node, Lisp_Object name)
+{
+  CHECK_STRING (name);
+  char *name_str = SSDATA (name);
+  TSNode ts_node = XTS_NODE (node)->node;
+  TSNode child
+    = ts_node_child_by_field_name (ts_node, name_str, strlen (name_str));
 
   if (ts_node_is_null(child))
     return Qnil;
@@ -272,10 +389,62 @@ DEFUN ("tree-sitter-node-child",
   return make_ts_node(XTS_NODE (node)->parser, child);
 }
 
+DEFUN ("tree-sitter-node-next-sibling",
+       Ftree_sitter_node_next_sibling,
+       Stree_sitter_node_next_sibling, 1, 2, 0,
+       doc: /* Return the next sibling of NODE.
+Return nil if there isn't any.  If NAMED is non-nil, look for named
+child only.  NAMED defaults to nil.  */)
+  (Lisp_Object node, Lisp_Object named)
+{
+  TSNode ts_node = XTS_NODE (node)->node;
+  TSNode sibling;
+  if (NILP (named))
+    sibling = ts_node_next_sibling (ts_node);
+  else
+    sibling = ts_node_next_named_sibling (ts_node);
+
+  if (ts_node_is_null(sibling))
+    return Qnil;
+
+  return make_ts_node(XTS_NODE (node)->parser, sibling);
+}
+
+DEFUN ("tree-sitter-node-prev-sibling",
+       Ftree_sitter_node_prev_sibling,
+       Stree_sitter_node_prev_sibling, 1, 2, 0,
+       doc: /* Return the previous sibling of NODE.
+Return nil if there isn't any.  If NAMED is non-nil, look for named
+child only.  NAMED defaults to nil.  */)
+  (Lisp_Object node, Lisp_Object named)
+{
+  TSNode ts_node = XTS_NODE (node)->node;
+  TSNode sibling;
+
+  if (NILP (named))
+    sibling = ts_node_prev_sibling (ts_node);
+  else
+    sibling = ts_node_prev_named_sibling (ts_node);
+
+  if (ts_node_is_null(sibling))
+    return Qnil;
+
+  return make_ts_node(XTS_NODE (node)->parser, sibling);
+}
+
+/* Query functions */
+
 /* Initialize the tree-sitter routines.  */
 void
 syms_of_tree_sitter (void)
 {
+  DEFSYM (Qtree_sitter_parser_p, "tree-sitter-parser-p");
+  DEFSYM (Qtree_sitter_node_p, "tree-sitter-node-p");
+  DEFSYM (Qnamed, "named");
+  DEFSYM (Qmissing, "missing");
+  DEFSYM (Qextra, "extra");
+  DEFSYM (Qhas_error, "has-error");
+
   DEFSYM (Qtree_sitter_parser_list, "tree-sitter-parser-list");
   DEFVAR_LISP ("ts-parser-list", Vtree_sitter_parser_list,
 		     doc: /* A list of tree-sitter parsers.
@@ -284,11 +453,20 @@ syms_of_tree_sitter (void)
   Vtree_sitter_parser_list = Qnil;
   Fmake_variable_buffer_local (Qtree_sitter_parser_list);
 
-
+  defsubr (&Stree_sitter_parser_p);
+  defsubr (&Stree_sitter_node_p);
   defsubr (&Stree_sitter_create_parser);
   defsubr (&Stree_sitter_parser_root_node);
   defsubr (&Stree_sitter_parse);
+
+  defsubr (&Stree_sitter_node_type);
   defsubr (&Stree_sitter_node_string);
   defsubr (&Stree_sitter_node_parent);
   defsubr (&Stree_sitter_node_child);
+  defsubr (&Stree_sitter_node_check);
+  defsubr (&Stree_sitter_node_field_name_for_child);
+  defsubr (&Stree_sitter_node_child_count);
+  defsubr (&Stree_sitter_node_child_by_field_name);
+  defsubr (&Stree_sitter_node_next_sibling);
+  defsubr (&Stree_sitter_node_prev_sibling);
 }
diff --git a/src/tree_sitter.h b/src/tree_sitter.h
index 0606f336cc..a7e2a2d670 100644
--- a/src/tree_sitter.h
+++ b/src/tree_sitter.h
@@ -37,7 +37,7 @@ #define EMACS_TREE_SITTER_H
   TSParser *parser;
   TSTree *tree;
   TSInput input;
-  TSInputEdit edit;
+  bool need_reparse;
 };
 
 /* A wrapper around a tree-sitter node.  */
@@ -78,11 +78,21 @@ XTS_NODE (Lisp_Object a)
   return XUNTAG (a, Lisp_Vectorlike, struct Lisp_TS_Node);
 }
 
-void
-ts_before_change (ptrdiff_t charpos, ptrdiff_t lendel);
+INLINE void
+CHECK_TS_PARSER (Lisp_Object parser)
+{
+  CHECK_TYPE (TS_PARSERP (parser), Qtree_sitter_parser_p, parser);
+}
+
+INLINE void
+CHECK_TS_NODE (Lisp_Object node)
+{
+  CHECK_TYPE (TS_NODEP (node), Qtree_sitter_node_p, node);
+}
 
 void
-ts_after_change (ptrdiff_t charpos, ptrdiff_t lendel, ptrdiff_t lenins);
+ts_record_change (ptrdiff_t start_byte, ptrdiff_t old_end_byte,
+		  ptrdiff_t new_end_byte);
 
 Lisp_Object
 make_ts_parser (struct buffer *buffer, TSParser *parser, TSTree *tree);
diff --git a/test/src/tree-sitter-tests.el b/test/src/tree-sitter-tests.el
new file mode 100644
index 0000000000..cb1c464d3a
--- /dev/null
+++ b/test/src/tree-sitter-tests.el
@@ -0,0 +1,106 @@
+;;; tree-sitter-tests.el --- tests for src/tree-sitter.c         -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ert)
+(require 'tree-sitter-json)
+
+(ert-deftest tree-sitter-basic-parsing ()
+  "Test basic parsing routines."
+  (with-temp-buffer
+    (let ((parser (tree-sitter-create-parser
+                   (current-buffer) (tree-sitter-json))))
+      (should
+       (eq parser (car tree-sitter-parser-list)))
+      (should
+       (equal (tree-sitter-node-string
+               (tree-sitter-parser-root-node parser))
+              "(ERROR)"))
+
+      (insert "[1,2,3]")
+      (should
+       (equal (tree-sitter-node-string
+               (tree-sitter-parser-root-node parser))
+              "(document (array (number) (number) (number)))"))
+
+      (goto-char (point-min))
+      (forward-char 3)
+      (insert "{\"name\": \"Bob\"},")
+      (should
+       (equal
+        (tree-sitter-node-string
+         (tree-sitter-parser-root-node parser))
+        "(document (array (number) (object (pair key: (string (string_content)) value: (string (string_content)))) (number) (number)))")))))
+
+(ert-deftest tree-sitter-node-api ()
+  "Tests for node API."
+  (with-temp-buffer
+    (insert "[1,2,{\"name\": \"Bob\"},3]")
+    (let (parser root-node doc-node object-node pair-node)
+      (setq parser (tree-sitter-create-parser
+                    (current-buffer) (tree-sitter-json)))
+      (setq root-node (tree-sitter-parser-root-node
+                       parser))
+      ;; `tree-sitter-node-type'.
+      (should (eq 'document (tree-sitter-node-type root-node)))
+      ;; `tree-sitter-node-check'.
+      (should (eq t (tree-sitter-node-check root-node 'named)))
+      (should (eq nil (tree-sitter-node-check root-node 'missing)))
+      (should (eq nil (tree-sitter-node-check root-node 'extra)))
+      (should (eq nil (tree-sitter-node-check root-node 'has-error)))
+      ;; `tree-sitter-node-child'.
+      (setq doc-node (tree-sitter-node-child root-node 0))
+      (should (eq 'array (tree-sitter-node-type doc-node)))
+      (should (equal (tree-sitter-node-string doc-node)
+                     "(array (number) (number) (object (pair key: (string (string_content)) value: (string (string_content)))) (number))"))
+      ;; `tree-sitter-node-child-count'.
+      (should (eql 9 (tree-sitter-node-child-count doc-node)))
+      (should (eql 4 (tree-sitter-node-child-count doc-node t)))
+      ;; `tree-sitter-node-field-name-for-child'.
+      (setq object-node (tree-sitter-node-child doc-node 2 t))
+      (setq pair-node (tree-sitter-node-child object-node 0 t))
+      (should (eq 'object (tree-sitter-node-type object-node)))
+      (should (eq 'pair (tree-sitter-node-type pair-node)))
+      (should (equal "key"
+                     (tree-sitter-node-field-name-for-child
+                      pair-node 0)))
+      ;; `tree-sitter-node-child-by-field-name'.
+      (should (equal "(string (string_content))"
+                     (tree-sitter-node-string
+                      (tree-sitter-node-child-by-field-name
+                       pair-node "key"))))
+      ;; `tree-sitter-node-next-sibling'.
+      (should (equal "(number)"
+                     (tree-sitter-node-string
+                      (tree-sitter-node-next-sibling object-node t))))
+      (should (equal "(\",\")"
+                     (tree-sitter-node-string
+                      (tree-sitter-node-next-sibling object-node))))
+      ;; `tree-sitter-node-prev-sibling'.
+      (should (equal "(number)"
+                     (tree-sitter-node-string
+                      (tree-sitter-node-prev-sibling object-node t))))
+      (should (equal "(\",\")"
+                     (tree-sitter-node-string
+                      (tree-sitter-node-prev-sibling object-node))))
+      )))
+
+(provide 'tree-sitter-tests)
+;;; tree-sitter-tests.el ends here
-- 
2.24.3 (Apple Git-128)

