pull/904/merge
sockeye-d 2025-11-24 13:35:04 +07:00 committed by GitHub
commit 75bd5f2e57
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
134 changed files with 109262 additions and 0 deletions

@ -77,6 +77,16 @@ fn main() {
src_dir: "vendored_parsers/tree-sitter-elvish-src",
extra_files: vec![],
},
TreeSitterParser {
name: "tree-sitter-gdscript",
src_dir: "vendored_parsers/tree-sitter-gdscript-src",
extra_files: vec!["scanner.c"],
},
TreeSitterParser {
name: "tree-sitter-godot-resource",
src_dir: "vendored_parsers/tree-sitter-godot-resource-src",
extra_files: vec!["scanner.c"],
},
TreeSitterParser {
name: "tree-sitter-hack",
src_dir: "vendored_parsers/tree-sitter-hack-src",

@ -36,8 +36,10 @@ pub(crate) enum Language {
EmacsLisp,
Erlang,
FSharp,
GDScript,
Gleam,
Go,
GodotResource,
Hack,
Hare,
Haskell,
@ -137,8 +139,10 @@ pub(crate) fn language_name(language: Language) -> &'static str {
EmacsLisp => "Emacs Lisp",
Erlang => "Erlang",
FSharp => "F#",
GDScript => "GDScript",
Gleam => "Gleam",
Go => "Go",
GodotResource => "Godot Resource",
Hack => "Hack",
Hare => "Hare",
Haskell => "Haskell",
@ -280,8 +284,10 @@ pub(crate) fn language_globs(language: Language) -> Vec<glob::Pattern> {
"rebar.lock",
],
FSharp => &["*.fs", "*.fsx", "*.fsi"],
GDScript => &["*.gd"],
Gleam => &["*.gleam"],
Go => &["*.go"],
GodotResource => &["*.tscn", "*.tres", "project.godot"],
Hack => &["*.hack", "*.hck", "*.hhi"],
Hare => &["*.ha"],
Haskell => &["*.hs"],

@ -71,6 +71,8 @@ pub(crate) struct TreeSitterConfig {
extern "C" {
fn tree_sitter_commonlisp() -> ts::Language;
fn tree_sitter_elvish() -> ts::Language;
fn tree_sitter_gdscript() -> ts::Language;
fn tree_sitter_godot_resource() -> ts::Language;
fn tree_sitter_hare() -> ts::Language;
fn tree_sitter_hack() -> ts::Language;
fn tree_sitter_janet_simple() -> ts::Language;
@ -389,6 +391,17 @@ pub(crate) fn from_language(language: guess::Language) -> TreeSitterConfig {
sub_languages: vec![],
}
}
GDScript => {
let language = unsafe { tree_sitter_gdscript() };
TreeSitterConfig {
language: language.clone(),
atom_nodes: vec![].into_iter().collect(),
delimiter_tokens: vec![("{", "}"), ("[", "]"), ("(", ")")],
highlight_query: ts::Query::new(&language, "").unwrap(),
sub_languages: vec![],
}
}
Gleam => {
let language_fn = tree_sitter_gleam::LANGUAGE;
let language = tree_sitter::Language::new(language_fn);
@ -418,6 +431,17 @@ pub(crate) fn from_language(language: guess::Language) -> TreeSitterConfig {
sub_languages: vec![],
}
}
GodotResource => {
let language = unsafe { tree_sitter_godot_resource() };
TreeSitterConfig {
language: language.clone(),
atom_nodes: vec![].into_iter().collect(),
delimiter_tokens: vec![("{", "}"), ("[", "]"), ("(", ")")],
highlight_query: ts::Query::new(&language, "").unwrap(),
sub_languages: vec![],
}
}
Hack => {
let language = unsafe { tree_sitter_hack() };
TreeSitterConfig {

@ -0,0 +1 @@
tree-sitter-gdscript/src/

@ -0,0 +1,46 @@
root = true
[*]
charset = utf-8
[*.{json,toml,yml,gyp}]
indent_style = space
indent_size = 2
[*.js]
indent_style = space
indent_size = 2
[*.scm]
indent_style = space
indent_size = 2
[*.{c,cc,h}]
indent_style = space
indent_size = 4
[*.rs]
indent_style = space
indent_size = 4
[*.{py,pyi}]
indent_style = space
indent_size = 4
[*.swift]
indent_style = space
indent_size = 4
[*.go]
indent_style = tab
indent_size = 8
[Makefile]
indent_style = tab
indent_size = 8
[parser.c]
indent_size = 2
[{alloc,array,parser}.h]
indent_size = 2

@ -0,0 +1,37 @@
* text=auto eol=lf
# Generated source files
src/*.json linguist-generated
src/parser.c linguist-generated
src/tree_sitter/* linguist-generated
# C bindings
bindings/c/* linguist-generated
CMakeLists.txt linguist-generated
Makefile linguist-generated
# Rust bindings
bindings/rust/* linguist-generated
Cargo.toml linguist-generated
Cargo.lock linguist-generated
# Node.js bindings
bindings/node/* linguist-generated
binding.gyp linguist-generated
package.json linguist-generated
package-lock.json linguist-generated
# Python bindings
bindings/python/** linguist-generated
setup.py linguist-generated
pyproject.toml linguist-generated
# Go bindings
bindings/go/* linguist-generated
go.mod linguist-generated
go.sum linguist-generated
# Swift bindings
bindings/swift/** linguist-generated
Package.swift linguist-generated
Package.resolved linguist-generated

@ -0,0 +1,73 @@
# This workflow will
# - test tree sitter grammar
# - upload build native binaries as an artifact for each major platform
# - download artifacts for each major platform and bundle them to be published
# to npm
# when a new version tag is pushed to the master branch.
name: Test Build Publish
on:
push:
tags: [v*]
jobs:
build_native_binaries:
strategy:
matrix:
# Use macos-14 for arm, however the artifact upload name conflicts with
# macos-latest. There's probably a way to crossbuild for arm with
# prebuildify.
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
# Why is npm only occassionally installing peer dependencies?
# I cannot get consistent peer dependency installs with the same command runs.
# Should I always explicitly run npm i tree-sitter? The general consensus is yes.
# But then, why does it install peer deps sometimes?
- run: |
node --version
npm --version
- run: npm ci --include=peer --include=optional --include=dev
- run: npm i tree-sitter
- run: npm run versions
- run: npm test
- run: npm run prebuild
# upload-artifact@v4 requires each artifact name to be unique.
- uses: actions/upload-artifact@v4
with:
name: prebuilds-${{ matrix.os }}
path: prebuilds
retention-days: 1
publish_npm:
needs: build_native_binaries
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
# https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-nodejs-packages#publishing-packages-to-the-npm-registry
# https://github.com/actions/setup-node/issues/342
# This is required to publish to npm.
registry-url: 'https://registry.npmjs.org'
- run: |
node --version
npm --version
- run: npm ci
# Download all artifacts and merge into prebuilds dir.
- uses: actions/download-artifact@v4
with:
path: prebuilds
pattern: prebuilds-*
merge-multiple: true
- run: ls -R prebuilds
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

@ -0,0 +1,22 @@
name: Test
on:
push:
paths:
- 'grammar.js'
- 'corpus/**'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- run: |
node --version
npm --version
- run: npm ci --include=dev --include=optional --include=peer
- run: npm test

@ -0,0 +1,41 @@
# Rust artifacts
target/
# Node artifacts
build/
prebuilds/
node_modules/
# Swift artifacts
.build/
# Go artifacts
_obj/
# Python artifacts
.venv/
dist/
*.egg-info
*.whl
# C artifacts
*.a
*.so
*.so.*
*.dylib
*.dll
*.pc
parser.exp
parser.lib
parser.obj
scanner.obj
# Grammar volatiles
*.wasm
*.obj
*.o
# Archives
*.tar.gz
*.tgz
*.zip

@ -0,0 +1,8 @@
corpus
examples
build
script
parser.exp
parser.lib
parser.obj
scanner.obj

@ -0,0 +1,58 @@
cmake_minimum_required(VERSION 3.13)
project(tree-sitter-gdscript
VERSION "6.0.0"
DESCRIPTION "Grammar for Godot's built-in scripting language."
HOMEPAGE_URL "https://github.com/prestonknopp/tree-sitter-gdscript"
LANGUAGES C)
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
option(TREE_SITTER_REUSE_ALLOCATOR "Reuse the library allocator" OFF)
set(TREE_SITTER_ABI_VERSION 14 CACHE STRING "Tree-sitter ABI version")
if(NOT ${TREE_SITTER_ABI_VERSION} MATCHES "^[0-9]+$")
unset(TREE_SITTER_ABI_VERSION CACHE)
message(FATAL_ERROR "TREE_SITTER_ABI_VERSION must be an integer")
endif()
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI")
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
--abi=${TREE_SITTER_ABI_VERSION}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Generating parser.c")
add_library(tree-sitter-gdscript src/parser.c)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c)
target_sources(tree-sitter-gdscript PRIVATE src/scanner.c)
endif()
target_include_directories(tree-sitter-gdscript PRIVATE src)
target_compile_definitions(tree-sitter-gdscript PRIVATE
$<$<BOOL:${TREE_SITTER_REUSE_ALLOCATOR}>:TREE_SITTER_REUSE_ALLOCATOR>
$<$<CONFIG:Debug>:TREE_SITTER_DEBUG>)
set_target_properties(tree-sitter-gdscript
PROPERTIES
C_STANDARD 11
POSITION_INDEPENDENT_CODE ON
SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}"
DEFINE_SYMBOL "")
configure_file(bindings/c/tree-sitter-gdscript.pc.in
"${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-gdscript.pc" @ONLY)
include(GNUInstallDirs)
install(FILES bindings/c/tree-sitter-gdscript.h
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/tree_sitter")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-gdscript.pc"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig")
install(TARGETS tree-sitter-gdscript
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
add_custom_target(ts-test "${TREE_SITTER_CLI}" test
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "tree-sitter test")

@ -0,0 +1,27 @@
[package]
name = "tree-sitter-gdscript"
description = "Grammar for Godot's built-in scripting language."
version = "6.0.0"
authors = ["Preston Knopp <prestonknopp@gmail.com>"]
license = "MIT"
readme = "README.md"
keywords = ["incremental", "parsing", "tree-sitter", "gdscript"]
categories = ["parsing", "text-editors"]
repository = "https://github.com/prestonknopp/tree-sitter-gdscript"
edition = "2021"
autoexamples = false
build = "bindings/rust/build.rs"
include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json"]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter-language = "0.1"
[build-dependencies]
cc = "1.1.22"
[dev-dependencies]
tree-sitter = "0.24.6"

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Max Brunsfeld
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,94 @@
ifeq ($(OS),Windows_NT)
$(error Windows is not supported)
endif
LANGUAGE_NAME := tree-sitter-gdscript
HOMEPAGE_URL := https://github.com/prestonknopp/tree-sitter-gdscript
VERSION := 6.0.0
# repository
SRC_DIR := src
TS ?= tree-sitter
# install directory layout
PREFIX ?= /usr/local
INCLUDEDIR ?= $(PREFIX)/include
LIBDIR ?= $(PREFIX)/lib
PCLIBDIR ?= $(LIBDIR)/pkgconfig
# source/object files
PARSER := $(SRC_DIR)/parser.c
EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c))
OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS))
# flags
ARFLAGS ?= rcs
override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC
# ABI versioning
SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER))
SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION)))
# OS-specific bits
ifeq ($(shell uname),Darwin)
SOEXT = dylib
SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT)
SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT)
LINKSHARED = -dynamiclib -Wl,-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SOEXTVER),-rpath,@executable_path/../Frameworks
else
SOEXT = so
SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR)
SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR)
LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER)
endif
ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),)
PCLIBDIR := $(PREFIX)/libdata/pkgconfig
endif
all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc
lib$(LANGUAGE_NAME).a: $(OBJS)
$(AR) $(ARFLAGS) $@ $^
lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS)
$(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@
ifneq ($(STRIP),)
$(STRIP) $@
endif
$(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in
sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \
-e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \
-e 's|@CMAKE_INSTALL_INCLUDEDIR@|$(INCLUDEDIR:$(PREFIX)/%=%)|' \
-e 's|@PROJECT_DESCRIPTION@|$(DESCRIPTION)|' \
-e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
$(PARSER): $(SRC_DIR)/grammar.json
$(TS) generate $^
install: all
install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
install -m644 bindings/c/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h
install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a
install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER)
ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR)
ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT)
uninstall:
$(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \
'$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \
'$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
clean:
$(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT)
test:
$(TS) test
.PHONY: all install uninstall clean test

@ -0,0 +1,37 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "TreeSitterGDScript",
products: [
.library(name: "TreeSitterGDScript", targets: ["TreeSitterGDScript"]),
],
dependencies: [
.package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.8.0"),
],
targets: [
.target(
name: "TreeSitterGDScript",
dependencies: [],
path: ".",
sources: [
"src/parser.c",
"src/scanner.c",
],
resources: [
.copy("queries")
],
publicHeadersPath: "bindings/swift",
cSettings: [.headerSearchPath("src")]
),
.testTarget(
name: "TreeSitterGDScriptTests",
dependencies: [
"SwiftTreeSitter",
"TreeSitterGDScript",
],
path: "bindings/swift/TreeSitterGDScriptTests"
)
],
cLanguageStandard: .c11
)

@ -0,0 +1,51 @@
tree-sitter-gdscript
====================
GDScript grammar for [tree-sitter][].
## Latest Godot Commit Syntactically Synced
Note: *Some commits may have been missed.*
```bash
git log --oneline --no-merges modules/gdscript
```
[6ae54fd787](https://github.com/godotengine/godot/commits/6ae54fd787)
## How To
- Test grammar
1. `npm run genTest`
- Test scanner
1. Edit "src/scanner.c"
1. `npm run test`, no need to generate.
- Build prebuilds
1. `npm run genTest`
1. `npm run prebuild`
- Build with node-gyp
1. `npm run genTest`
1. `npm install node-gyp`
1. `node-gyp rebuild`
- Edit
1. Write tests in corpus to express behavior.
1. Make grammar or scanner edits.
1. See above for running tests.
1. `npm run format`
1. Commit changes.
- If commit is an issue fix, prefix message with `fix(#<issue-number>):`
- List the rules changed in commit message.
- Note what rules need to be updated in [nvim-treesitter][] queries.
1. Commit generated files with the latest non-wip commit.
1. Push
- Release
1. Manually edit version in package files: CMakeLists.txt, Cargo.toml,
Makefile, pyproject.toml, tree-sitter.json
1. `npm version <major, minor, patch> -m "<> version bump"`
1. `git push --follow-tags`
Note: `node-gyp-build` will check for binaries in both `build` and `prebuilds`
directories.
[tree-sitter]: https://github.com/tree-sitter/tree-sitter
[nvim-treesitter]: https://github.com/nvim-treesitter/nvim-treesitter

@ -0,0 +1,30 @@
{
"targets": [
{
"target_name": "tree_sitter_gdscript_binding",
"dependencies": [
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
],
"include_dirs": [
"src",
],
"sources": [
"bindings/node/binding.cc",
"src/parser.c",
"src/scanner.c",
],
"conditions": [
["OS!='win'", {
"cflags_c": [
"-std=c11",
],
}, { # OS == "win"
"cflags_c": [
"/std:c11",
"/utf-8",
],
}],
],
}
]
}

@ -0,0 +1,16 @@
#ifndef TREE_SITTER_GDSCRIPT_H_
#define TREE_SITTER_GDSCRIPT_H_
typedef struct TSLanguage TSLanguage;
#ifdef __cplusplus
extern "C" {
#endif
const TSLanguage *tree_sitter_gdscript(void);
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_GDSCRIPT_H_

@ -0,0 +1,10 @@
prefix=@CMAKE_INSTALL_PREFIX@
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: tree-sitter-gdscript
Description: @PROJECT_DESCRIPTION@
URL: @PROJECT_HOMEPAGE_URL@
Version: @PROJECT_VERSION@
Libs: -L${libdir} -ltree-sitter-gdscript
Cflags: -I${includedir}

@ -0,0 +1,15 @@
package tree_sitter_gdscript
// #cgo CFLAGS: -std=c11 -fPIC
// #include "../../src/parser.c"
// #if __has_include("../../src/scanner.c")
// #include "../../src/scanner.c"
// #endif
import "C"
import "unsafe"
// Get the tree-sitter Language for this grammar.
func Language() unsafe.Pointer {
return unsafe.Pointer(C.tree_sitter_gdscript())
}

@ -0,0 +1,15 @@
package tree_sitter_gdscript_test
import (
"testing"
tree_sitter "github.com/tree-sitter/go-tree-sitter"
tree_sitter_gdscript "github.com/prestonknopp/tree-sitter-gdscript/bindings/go"
)
func TestCanLoadGrammar(t *testing.T) {
language := tree_sitter.NewLanguage(tree_sitter_gdscript.Language())
if language == nil {
t.Errorf("Error loading GDScript grammar")
}
}

@ -0,0 +1,9 @@
# This module, i.e. the nim bindings, can be placed anywhere in your nim project
# or kept in the bindings directory.
#
# However the following assumes that when compiling your project or main module that
# "tree-sitter-gdscript/" is accessible from the current working directory.
{.passC: "-Itree-sitter-gdscript/src".}
{.compile: "tree-sitter-gdscript/src/parser.c".}
{.compile: "tree-sitter-gdscript/src/scanner.c".}
proc tree_sitter_gdscript*(): pointer {.importc.}

@ -0,0 +1,20 @@
#include <napi.h>
typedef struct TSLanguage TSLanguage;
extern "C" TSLanguage *tree_sitter_gdscript();
// "tree-sitter", "language" hashed with BLAKE2
const napi_type_tag LANGUAGE_TYPE_TAG = {
0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
};
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports["name"] = Napi::String::New(env, "gdscript");
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_gdscript());
language.TypeTag(&LANGUAGE_TYPE_TAG);
exports["language"] = language;
return exports;
}
NODE_API_MODULE(tree_sitter_gdscript_binding, Init)

@ -0,0 +1,9 @@
const assert = require("node:assert");
const { test } = require("node:test");
const Parser = require("tree-sitter");
test("can load grammar", () => {
const parser = new Parser();
assert.doesNotThrow(() => parser.setLanguage(require(".")));
});

@ -0,0 +1,28 @@
type BaseNode = {
type: string;
named: boolean;
};
type ChildNode = {
multiple: boolean;
required: boolean;
types: BaseNode[];
};
type NodeInfo =
| (BaseNode & {
subtypes: BaseNode[];
})
| (BaseNode & {
fields: { [name: string]: ChildNode };
children: ChildNode[];
});
type Language = {
name: string;
language: unknown;
nodeTypeInfo: NodeInfo[];
};
declare const language: Language;
export = language;

@ -0,0 +1,11 @@
const root = require("path").join(__dirname, "..", "..");
module.exports =
typeof process.versions.bun === "string"
// Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-gdscript.node`)
: require("node-gyp-build")(root);
try {
module.exports.nodeTypeInfo = require("../../src/node-types.json");
} catch (_) {}

@ -0,0 +1,11 @@
from unittest import TestCase
import tree_sitter, tree_sitter_gdscript
class TestLanguage(TestCase):
def test_can_load_grammar(self):
try:
tree_sitter.Language(tree_sitter_gdscript.language())
except Exception:
self.fail("Error loading GDScript grammar")

@ -0,0 +1,42 @@
"""Grammar for Godot's built-in scripting language."""
from importlib.resources import files as _files
from ._binding import language
def _get_query(name, file):
query = _files(f"{__package__}.queries") / file
globals()[name] = query.read_text()
return globals()[name]
def __getattr__(name):
# NOTE: uncomment these to include any queries that this grammar contains:
# if name == "HIGHLIGHTS_QUERY":
# return _get_query("HIGHLIGHTS_QUERY", "highlights.scm")
# if name == "INJECTIONS_QUERY":
# return _get_query("INJECTIONS_QUERY", "injections.scm")
# if name == "LOCALS_QUERY":
# return _get_query("LOCALS_QUERY", "locals.scm")
# if name == "TAGS_QUERY":
# return _get_query("TAGS_QUERY", "tags.scm")
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = [
"language",
# "HIGHLIGHTS_QUERY",
# "INJECTIONS_QUERY",
# "LOCALS_QUERY",
# "TAGS_QUERY",
]
def __dir__():
return sorted(__all__ + [
"__all__", "__builtins__", "__cached__", "__doc__", "__file__",
"__loader__", "__name__", "__package__", "__path__", "__spec__",
])

@ -0,0 +1,10 @@
from typing import Final
# NOTE: uncomment these to include any queries that this grammar contains:
# HIGHLIGHTS_QUERY: Final[str]
# INJECTIONS_QUERY: Final[str]
# LOCALS_QUERY: Final[str]
# TAGS_QUERY: Final[str]
def language() -> object: ...

@ -0,0 +1,27 @@
#include <Python.h>
typedef struct TSLanguage TSLanguage;
TSLanguage *tree_sitter_gdscript(void);
static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) {
return PyCapsule_New(tree_sitter_gdscript(), "tree_sitter.Language", NULL);
}
static PyMethodDef methods[] = {
{"language", _binding_language, METH_NOARGS,
"Get the tree-sitter language for this grammar."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "_binding",
.m_doc = NULL,
.m_size = -1,
.m_methods = methods
};
PyMODINIT_FUNC PyInit__binding(void) {
return PyModule_Create(&module);
}

@ -0,0 +1,19 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.std("c11").include(src_dir);
#[cfg(target_env = "msvc")]
c_config.flag("-utf-8");
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
let scanner_path = src_dir.join("scanner.c");
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
c_config.compile("tree-sitter-gdscript");
}

@ -0,0 +1,53 @@
//! This crate provides GDScript language support for the [tree-sitter][] parsing library.
//!
//! Typically, you will use the [LANGUAGE][] constant to add this language to a
//! tree-sitter [Parser][], and then use the parser to parse some code:
//!
//! ```
//! let code = r#"
//! "#;
//! let mut parser = tree_sitter::Parser::new();
//! let language = tree_sitter_gdscript::LANGUAGE;
//! parser
//! .set_language(&language.into())
//! .expect("Error loading GDScript parser");
//! let tree = parser.parse(code, None).unwrap();
//! assert!(!tree.root_node().has_error());
//! ```
//!
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/
use tree_sitter_language::LanguageFn;
extern "C" {
fn tree_sitter_gdscript() -> *const ();
}
/// The tree-sitter [`LanguageFn`][LanguageFn] for this grammar.
///
/// [LanguageFn]: https://docs.rs/tree-sitter-language/*/tree_sitter_language/struct.LanguageFn.html
pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_gdscript) };
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
// NOTE: uncomment these to include any queries that this grammar contains:
// pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&super::LANGUAGE.into())
.expect("Error loading GDScript parser");
}
}

@ -0,0 +1,16 @@
#ifndef TREE_SITTER_GDSCRIPT_H_
#define TREE_SITTER_GDSCRIPT_H_
typedef struct TSLanguage TSLanguage;
#ifdef __cplusplus
extern "C" {
#endif
const TSLanguage *tree_sitter_gdscript(void);
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_GDSCRIPT_H_

@ -0,0 +1,12 @@
import XCTest
import SwiftTreeSitter
import TreeSitterGDScript
final class TreeSitterGDScriptTests: XCTestCase {
func testCanLoadGrammar() throws {
let parser = Parser()
let language = Language(language: tree_sitter_gdscript())
XCTAssertNoThrow(try parser.setLanguage(language),
"Error loading GDScript grammar")
}
}

@ -0,0 +1 @@
(V(1, 2) + V(2, 3)).b().c().d = ('Hello' + 'World').b.c.d()

@ -0,0 +1,16 @@
extends Node
func _ready():
var x := 2
for i in range(x):
prints(i)
while x > 0:
print(x)
if x > 0:
print("if test")
elif x < 0:
print("if test")
else:
print("if test")

@ -0,0 +1,76 @@
# Excerpted from:
# https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html
class_name StateMachine
extends Node
## Hierarchical State machine for the player.
##
## Initializes states and delegates engine callbacks ([method Node._physics_process],
## [method Node._unhandled_input]) to the state.
signal state_changed(previous, new)
@export var initial_state: Node
var is_active = true:
set = set_is_active
@onready var _state = initial_state:
set = set_state
@onready var _state_name = _state.name
func _init():
add_to_group("state_machine")
func _enter_tree():
print("this happens before the ready method!")
func _ready():
state_changed.connect(_on_state_changed)
_state.enter()
func _unhandled_input(event):
_state.unhandled_input(event)
func _physics_process(delta):
_state.physics_process(delta)
func transition_to(target_state_path, msg={}):
if not has_node(target_state_path):
return
var target_state = get_node(target_state_path)
assert(target_state.is_composite == false)
_state.exit()
self._state = target_state
_state.enter(msg)
Events.player_state_changed.emit(_state.name)
func set_is_active(value):
is_active = value
set_physics_process(value)
set_process_unhandled_input(value)
set_block_signals(not value)
func set_state(value):
_state = value
_state_name = _state.name
func _on_state_changed(previous, new):
print("state changed")
state_changed.emit()
class State:
var foo = 0
func _init():
print("Hello!")

@ -0,0 +1,5 @@
func hello():
match 0:
0 when false: print("does not run")
## Invalid:
# 1 when true, 2 when false: print("This does not parse")

@ -0,0 +1,11 @@
# Error: @export cannot be applied to static var
# but annotations are still parsed.
# @export static var hello: int
static var foo = 1
class Foo:
static var bar
static var name := "Me"
static var value: int

@ -0,0 +1,3 @@
func hello():
for i: int in [1,2,3]:
pass

@ -0,0 +1,18 @@
take(func name():
print('hello')
return 1
pass
1 + 1
,
""
)
take(func name():
1 + 1; pass
, "")
take(func name():
pass
pass, "")
take(func name(): "")

@ -0,0 +1,2 @@
func _init(arg1, arg2).(arg1, arg2):
pass

@ -0,0 +1,62 @@
# Verified with v4.0.beta17.official [c40020513]
# FIXME: tree-sitter-gdscript can't parse this yet.
var p = func(): if true: pass else: pass
# If stmts can be inline. (They do not eval to their last expr.)
t(func(): if true: 'string' else: 100)
# GDScript v2 cannot parse this:
#if true: print(1); print(2) else: print(3); print(4)
# But, can parse this:
# prints:
# 1
# 2
var p = func(): if true: print(1); print(2) else: print(3); print(4)
# prints:
# outer 3
#var c = func(): if true: if false: print('inner 1'); print('inner 2') else: print('outer 3') else: print(4)
# some whitespace handling between statement groups.
# returns: 2
var x = func hello(): var b = 2; return b;
if true: print('a') ; print('b');
else: print('c'); var a: int = 0; a + 1; return a
# prints:
# a
# b
# func bodies end at a comma. remaining args can follow.
# no newline needed.
take(func(): var a = 2; return x, 1)
take(func():
print("hello world")
pass, "")
# func bodies also end at a paren.
t(func():
print('hello')
1 + 1)
# These are valid.
t(func():
pass
)
t(
func(): pass,
func():
pass
)
# types can be specified
var d = func(hello: int, string: String) -> int:
return string.length() + hello
t(func name(a: int, b: int) -> int: return a + b)
t(func name(a: int, b: int) -> int:
return a + b, "last argument")

@ -0,0 +1,17 @@
match x:
1: pass
_: pass
TYPE_ARRAY: pass
var new_var: pass
[]: pass
[1, 3, "test", null]: pass
[var start, _]: pass
[42, ..]: pass
{}: pass
{"name": "Dennis"}: pass
{"name": "Dennis", "age": var age}: pass
{"key": "godotisawesome", ..}: pass
1, 2, 3: pass

@ -0,0 +1,7 @@
var hello:
set(v):
print()
pass
get():
print()
pass

@ -0,0 +1,60 @@
extends SceneTree
# interesting, godot looks for a get_node() func when '$' is used on a non node?
func get_node(a):
return null
# func hello(one,):
# print('hello')
class n extends Node:
# no: remote static func rfunc():
# no: static remote func rfunc():
static func rfunc():
pass
# no: master export var world = 'world'
remotesync var hello = 'world'
export remote var butt = 'world'
var x setget setter,getter
var y setget setter
var a setget ,getter
func setter(a):pass
func getter():pass
func _initialize():
var b: String = "hello"
print(b)
var butt = 'buttface'
var next = 'next'
var d = {
'hello': 'world',
world = 'hello',
butt: 'buttt',
butt + next: 'buttnext',
}
var d2 = {hello='hello',}
print(1 % 5)
print(d)
var n = $node
n = $ node
n = $'1/a'
# var x = n is get_node(a)
var res = 'Response'
match 'Response':
res: print('response')
# no:
# match {'Response':'ok'}:
# {res: 'ok'}: print('okay')
# no:
# match {'Response':'ok'}:
# {Response = 'ok'}: print('okay')

@ -0,0 +1,5 @@
module github.com/prestonknopp/tree-sitter-gdscript
go 1.22
require github.com/tree-sitter/go-tree-sitter v0.24.0

@ -0,0 +1,930 @@
const PREC = {
typed_parameter: -1,
conditional: -1,
parenthesized_expression: 1,
or: 3,
and: 4,
in: 5,
compare: 6,
bitwise_or: 7,
bitwise_and: 8,
xor: 9,
shift: 10,
plus: 11,
times: 12,
power: 13,
unary: 14,
is: 15,
as: 16,
call: 17,
attribute: 18,
attribute_expression: 19,
type: 20,
};
module.exports = grammar({
name: "gdscript",
word: ($) => $._identifier,
extras: ($) => [$.comment, /[\s\uFEFF\u2060\u200B]/, $.line_continuation],
externals: ($) => [
$._newline,
$._indent,
$._dedent,
$._string_start,
$._string_content,
$._string_end,
$._string_name_start,
$._node_path_start,
// Allow the external scanner to check for the validity of closing brackets
// so that it can avoid returning dedent tokens between brackets.
"]",
")",
"}",
// Allow the external scanner to check for valid comma and colon tokens when
// scanning for a $._body_end token.
",",
// Allowing the scanner to check if colon is a valid token when
// parsing body ends works for expected cases. One case is using a lambda as
// a dictionary key e.g. `{func(): pass: 'value'}`.
// However, it breaks nested if else chains.
/* ":", */
$._body_end,
],
inline: ($) => [$._simple_statement, $._compound_statement],
supertypes: ($) => [
$._compound_statement,
$._pattern,
$._expression,
$._primary_expression,
$._attribute_expression,
$._parameters,
],
rules: {
source: ($) => repeat($._statement),
// -----------------------------------------------------------------------------
// - Atoms -
// -----------------------------------------------------------------------------
_identifier: ($) => /[a-zA-Z_][a-zA-Z_0-9]*/,
// any "symbol"
identifier: ($) => $._identifier,
// named symbol of a statement
// such as a function name or class name
name: ($) => $._identifier,
// Code region syntax, parsed to offer code folding support (these are #region and #endregion marks)
region_start: ($) =>
seq(token(prec(100, "#region")), optional($.region_label)),
region_end: ($) =>
token(seq(prec(100, "#endregion"), optional(/[^\r\n]*/))),
region_label: ($) => /[^\r\n]+/,
comment: ($) => token(seq("#", /.*/)),
true: ($) => "true",
false: ($) => "false",
null: ($) => "null",
static_keyword: ($) => "static",
remote_keyword: ($) =>
choice(
"remote",
"master",
"puppet",
"remotesync",
"mastersync",
"puppetsync",
),
escape_sequence: ($) =>
token(
seq(
"\\",
choice(
/u[a-fA-F\d]{4}/,
/U[a-fA-F\d]{6}/,
/x[a-fA-F\d]{2}/,
/o\d{3}/,
/\r\n/,
/[^uxo]/,
),
),
),
string: ($) =>
seq(
alias($._string_start, '"'),
repeat(choice($.escape_sequence, $._string_content)),
alias($._string_end, '"'),
),
float: ($) => {
const digits = repeat1(/[0-9]+_?/);
const exponent = seq(/[eE][\+-]?/, digits);
return token(
choice(
seq(digits, ".", optional(digits), optional(exponent)),
seq(optional(digits), ".", digits, optional(exponent)),
seq(digits, exponent),
),
);
},
integer: ($) =>
token(
choice(
seq(choice("0x", "0X"), repeat1(/_?[A-Fa-f0-9]+/)),
seq(choice("0o", "0O"), repeat1(/_?[0-7]+/)),
seq(choice("0b", "0B"), repeat1(/_?[0-1]+/)),
repeat1(/[0-9]+_?/),
),
),
string_name: ($) =>
seq(
alias($._string_name_start, '&"'),
repeat(choice($.escape_sequence, $._string_content)),
alias($._string_end, '"'),
),
node_path: ($) =>
seq(
alias($._node_path_start, '^"'),
repeat(choice($.escape_sequence, $._string_content)),
alias($._string_end, '"'),
),
get_node: ($) =>
prec.right(
seq(
choice(
seq(
"$",
choice(
alias($.string, "value"),
seq(
optional("/"),
$._identifier,
repeat(seq("/", $._identifier)),
),
),
),
seq(
"%",
choice(
alias($.string, "value"),
seq($._identifier, repeat(seq("/", $._identifier))),
),
),
),
),
),
// -----------------------------------------------------------------------------
// - Type -
// -----------------------------------------------------------------------------
// Higher precedence is required to avoid conflicts with the "in" keyword in
// $.for_statement.
type: ($) =>
prec(PREC.type, choice($.attribute, $.identifier, $.subscript)),
// -----------------------------------------------------------------------------
// - Statements -
// -----------------------------------------------------------------------------
_statement: ($) => choice($._simple_statements, $._compound_statement),
body: ($) =>
choice(
$._simple_statements,
$._newline,
$._body_end,
seq(
$._indent,
repeat($._statement),
choice($._body_end, $._dedent),
),
),
// Simple statements
_simple_statements: ($) =>
seq(
trailSep1($._simple_statement, repeat1(";")),
choice($._newline, $._body_end),
),
_simple_statement: ($) =>
choice(
$._annotations,
$.signal_statement,
$.class_name_statement,
$.extends_statement,
$.expression_statement,
$.export_variable_statement,
$.onready_variable_statement,
$.variable_statement,
$.const_statement,
$.return_statement,
$.pass_statement,
$.break_statement,
$.breakpoint_statement,
$.continue_statement,
$.region_start,
$.region_end,
),
expression_statement: ($) =>
choice($._expression, $.assignment, $.augmented_assignment),
// -- Annotation
annotation: ($) =>
prec.right(
seq("@", $.identifier, optional(field("arguments", $.arguments))),
),
// The syntax tree looks better when annotations are grouped in a container
// node in contexts like variable_statement and function_definition.
_annotations: ($) => repeat1($.annotation),
annotations: ($) => $._annotations,
// -- Variables
inferred_type: ($) => choice(":=", seq(":", "=")),
_variable_assignment: ($) => seq("=", field("value", $._rhs_expression)),
_variable_inferred_type_assignment: ($) =>
seq(field("type", $.inferred_type), field("value", $._rhs_expression)),
_variable_typed_assignment: ($) =>
seq(":", field("type", $.type), "=", field("value", $._rhs_expression)),
_variable_typed_definition: ($) =>
choice(seq(":", field("type", $.type)), $._variable_typed_assignment),
// -- SetGet
set_body: ($) => seq("set", $.parameters, ":", field("body", $.body)),
get_body: ($) =>
seq(
"get",
// Let's alias parameters as an un-named node since
// get does not take any parameters.
optional(alias($.parameters, "()")),
":",
field("body", $.body),
),
_set_assign: ($) => seq("set", "=", field("set", $.setter)),
_get_assign: ($) => seq("get", "=", field("get", $.getter)),
_setget_assign: ($) =>
choice(
seq($._set_assign, optional(seq(",", $._get_assign))),
seq($._get_assign, optional(seq(",", $._set_assign))),
),
_setget_body: ($) =>
seq(
":",
choice(
$._setget_assign,
seq(
$._indent,
choice(
seq(field("set", $.set_body), optional(field("get", $.get_body))),
seq(field("get", $.get_body), optional(field("set", $.set_body))),
$._setget_assign,
),
$._dedent,
),
),
),
setter: ($) => $._identifier,
getter: ($) => $._identifier,
setget: ($) =>
choice(
$._setget_body,
seq(
"setget",
choice($.setter, seq($.setter, ",", $.getter), seq(",", $.getter)),
),
),
_variable_statement: ($) =>
seq(
optional($.annotations),
optional(field("static", $.static_keyword)),
"var",
field("name", $.name),
optional(
choice(
$._variable_typed_definition,
$._variable_inferred_type_assignment,
$._variable_assignment,
),
),
optional(field("setget", $.setget)),
),
variable_statement: ($) =>
seq(optional($.remote_keyword), $._variable_statement),
export_variable_statement: ($) =>
seq(
"export",
optional(field("arguments", $.arguments)),
optional(choice("onready", $.remote_keyword)),
$._variable_statement,
),
onready_variable_statement: ($) => seq("onready", $._variable_statement),
const_statement: ($) =>
seq(
"const",
field("name", $.name),
choice(
$._variable_inferred_type_assignment,
$._variable_typed_assignment,
$._variable_assignment,
),
),
return_statement: ($) => seq("return", optional($._rhs_expression)),
pass_statement: ($) => prec.left("pass"),
break_statement: ($) => prec.left("break"),
breakpoint_statement: ($) => "breakpoint",
continue_statement: ($) => prec.left("continue"),
signal_statement: ($) =>
seq(
"signal",
field("name", $.name),
optional(field("parameters", $.parameters)),
),
class_name_statement: ($) =>
seq(
optional($.annotations),
"class_name",
field("name", $.name),
optional(seq(",", field("icon_path", $.string))),
field("extends", optional($.extends_statement)),
),
extends_statement: ($) =>
prec(PREC.type, seq("extends", choice($.string, $.type))),
_compound_statement: ($) =>
choice(
$.if_statement,
$.for_statement,
$.while_statement,
$.function_definition,
$.constructor_definition,
$.class_definition,
$.enum_definition,
$.match_statement,
),
if_statement: ($) =>
seq(
"if",
field("condition", $._expression),
":",
field("body", $.body),
repeat(field("alternative", $.elif_clause)),
optional(field("alternative", $.else_clause)),
),
elif_clause: ($) =>
seq(
"elif",
field("condition", $._expression),
":",
field("body", $.body),
),
else_clause: ($) => seq("else", ":", field("body", $.body)),
for_statement: ($) =>
seq(
"for",
field("left", $.identifier),
optional(seq(":", field("type", $.type))),
"in",
field("right", $._expression),
":",
field("body", $.body),
),
while_statement: ($) =>
seq(
"while",
field("condition", $._expression),
":",
field("body", $.body),
),
class_definition: ($) =>
seq(
optional($.annotations),
"class",
field("name", $.name),
optional(field("extends", $.extends_statement)),
":",
field("body", $.class_body),
),
class_body: ($) =>
choice(
$._class_member,
$._newline,
$._body_end,
seq(
$._indent,
repeat($._class_member),
choice($._body_end, $._dedent),
),
),
// A class body can only directly contain class members. Then these class
// members can contain statements in their bodies, but not directly in the
// class.
_class_member: ($) =>
choice($._simple_class_members, $._compound_class_member),
_simple_class_members: ($) =>
seq(
trailSep1($._simple_class_member, repeat1(";")),
choice($._newline, $._body_end),
),
_simple_class_member: ($) =>
choice(
$.const_statement,
$.extends_statement,
$.pass_statement,
$.signal_statement,
$.variable_statement,
),
_compound_class_member: ($) =>
choice($.class_definition, $.enum_definition, $.function_definition),
// -- Enum
enum_definition: ($) =>
seq(
"enum",
optional(field("name", $.name)),
field("body", $.enumerator_list),
),
enumerator_list: ($) => seq("{", trailCommaSep1($.enumerator), "}"),
_enumerator_expression: ($) =>
choice(
$.integer,
$.binary_operator,
$.identifier,
$.unary_operator,
$.attribute,
$.subscript,
$.call,
$.parenthesized_expression,
),
enumerator: ($) =>
seq(
field("left", $.identifier),
optional(seq("=", field("right", $._enumerator_expression))),
),
// -----------------------------------------------------------------------------
// - Match -
// -----------------------------------------------------------------------------
match_statement: ($) =>
seq(
"match",
field("value", $._expression),
":",
field("body", $.match_body),
),
match_body: ($) =>
seq(
$._indent,
// Annotations are generally supported as statements throughout code but
// as match blocks are expressions, we need to explicitly allow them
// here. The pattern section body itself supports statements (thus annotations).
repeat1(
seq(
optional(repeat(seq($.annotation, optional($._newline)))),
$.pattern_section,
),
),
$._dedent,
),
// Sources:
// - https://github.com/godotengine/godot-proposals/issues/4775
// - https://github.com/godotengine/godot/pull/80085
//
// One guard per section. Meaning Comma separated patterns cannot each have
// a guard.
pattern_guard: ($) => seq("when", $._expression),
pattern_section: ($) =>
seq(
commaSep1($._pattern),
optional($.pattern_guard),
":",
field("body", $.body),
),
_pattern: ($) =>
choice(
$._primary_expression,
$.conditional_expression,
$.pattern_binding,
),
// Rather than creating distinct pattern array, dictionary, and expression
// rules, we insert $.pattern_binding and $.pattern_open_ending into the
// $.array and $.dictionary rules. Although, they are only valid in the
// context of a pattern, this keeps the grammar simpler and allows us to
// have arbitrary expressions in patterns.
//
// Additionally, $.dictionary accepts comma separated list of keys mixed
// with pairs. This is also only valid in patterns and keeps the grammar a
// bit simpler.
pattern_binding: ($) => seq("var", $.identifier),
pattern_open_ending: ($) => "..",
// -----------------------------------------------------------------------------
// - Expressions -
// -----------------------------------------------------------------------------
_expression: ($) => choice($._primary_expression, $.conditional_expression),
_primary_expression: ($) =>
choice(
$.binary_operator,
$.identifier,
$.string,
$.integer,
$.float,
$.true,
$.false,
$.null,
$.unary_operator,
$.string_name,
$.node_path,
$.get_node,
$.attribute,
$.subscript,
$.base_call,
$.call,
$.array,
$.dictionary,
$.parenthesized_expression,
$.await_expression,
),
_rhs_expression: ($) => choice($._expression, $.lambda),
// This makes an attribute's ast linear
// When attribute is used inside $.attribute it becomes recursive spaghetti
_attribute_expression: ($) =>
prec(
PREC.attribute_expression,
choice(
$.binary_operator,
$.identifier,
$.string,
$.integer,
$.float,
$.true,
$.false,
$.null,
$.unary_operator,
$.node_path,
$.get_node,
$.subscript,
$.base_call,
$.call,
$.array,
$.dictionary,
$.parenthesized_expression,
),
),
// -- Operators
binary_operator: ($) => {
// Inspired by tree-sitter-c
const operators = [
[seq("not", "in"), PREC.in],
["in", PREC.in],
["and", PREC.and],
["&&", PREC.and],
["or", PREC.or],
["||", PREC.or],
["+", PREC.plus],
["-", PREC.plus],
["*", PREC.times],
["/", PREC.times],
["**", PREC.times],
["%", PREC.times],
["|", PREC.bitwise_or],
["&", PREC.bitwise_and],
["^", PREC.xor],
["<<", PREC.shift],
[">>", PREC.shift],
["<", PREC.compare],
["<=", PREC.compare],
["==", PREC.compare],
["!=", PREC.compare],
[">=", PREC.compare],
[">", PREC.compare],
["as", PREC.as],
[seq("is", "not"), PREC.is],
["is", PREC.is],
];
const choices = operators.map(([operator, precedence]) => {
return prec.left(
precedence,
seq(
field("left", $._primary_expression),
field("op", operator),
field("right", $._primary_expression),
),
);
});
return choice(...choices);
},
unary_operator: ($) =>
choice(
prec(PREC.unary, seq(choice("not", "!"), $._primary_expression)),
prec(PREC.unary, seq("-", $._primary_expression)),
prec(PREC.unary, seq("+", $._primary_expression)),
prec(PREC.unary, seq("~", $._primary_expression)),
),
// -- Accessors
subscript_arguments: ($) =>
seq("[", trailCommaSep1($._rhs_expression), "]"),
subscript: ($) =>
// The high precedence resolves ambiguity when parsing a definition
// followed by code on the same line like class C: var x = my_array[0]
prec(
PREC.attribute,
seq($._primary_expression, field("arguments", $.subscript_arguments)),
),
attribute_call: ($) =>
prec(PREC.attribute, seq($.identifier, field("arguments", $.arguments))),
attribute_subscript: ($) =>
prec(
PREC.attribute,
seq($.identifier, field("arguments", $.subscript_arguments)),
),
attribute: ($) =>
prec(
PREC.attribute,
seq(
$._attribute_expression,
repeat1(
seq(
".",
choice($.attribute_subscript, $.attribute_call, $.identifier),
),
),
),
),
conditional_expression: ($) =>
prec.right(
PREC.conditional,
seq(
field("left", $._expression),
"if",
field("condition", $._expression),
"else",
field("right", $._expression),
),
),
parenthesized_expression: ($) =>
prec(PREC.parenthesized_expression, seq("(", $._rhs_expression, ")")),
// -----------------------------------------------------------------------------
// - Await -
// -----------------------------------------------------------------------------
await_expression: ($) => seq("await", $._expression),
// -----------------------------------------------------------------------------
// - Assignment -
// -----------------------------------------------------------------------------
assignment: ($) =>
seq(field("left", $._expression), "=", field("right", $._rhs_expression)),
augmented_assignment: ($) =>
seq(
field("left", $._expression),
field(
"op",
choice(
"+=",
"-=",
"*=",
"/=",
"**=",
"%=",
">>=",
"<<=",
"&=",
"^=",
"|=",
),
),
field("right", $._rhs_expression),
),
// -----------------------------------------------------------------------------
// - Data Structs -
// -----------------------------------------------------------------------------
pair: ($) =>
seq(
choice(
seq(field("left", $._rhs_expression), ":"), // Lambdas are allowed here.
seq(field("left", $.identifier), "="),
),
field("value", choice($._rhs_expression, $.pattern_binding)),
),
// See $.pattern_binding for more information.
dictionary: ($) =>
seq(
"{",
optional(
trailCommaSep1(
choice(
$.pair,
// This allows dictionaries in pattern sections to support "key"
// only pattern matching:
// match { "key_to_match": some_value }:
// { "key_to_match" }: print("Matches here!")
$._primary_expression,
),
),
),
optional($.pattern_open_ending),
"}",
),
array: ($) =>
seq(
"[",
optional(trailCommaSep1(choice($._rhs_expression, $.pattern_binding))),
optional($.pattern_open_ending),
"]",
),
// -----------------------------------------------------------------------------
// - Function Definition -
// -----------------------------------------------------------------------------
typed_parameter: ($) =>
prec(PREC.typed_parameter, seq($.identifier, ":", field("type", $.type))),
default_parameter: ($) =>
seq($.identifier, "=", field("value", $._rhs_expression)),
typed_default_parameter: ($) =>
prec(
PREC.typed_parameter,
choice(
seq(
$.identifier,
":",
field("type", $.type),
"=",
field("value", $._rhs_expression),
),
seq(
$.identifier,
field("type", $.inferred_type),
field("value", $._rhs_expression),
),
),
),
variadic_parameter: ($) =>
seq(
"...",
$._parameters,
),
_parameters: ($) =>
choice(
$.identifier,
$.typed_parameter,
$.default_parameter,
$.typed_default_parameter,
$.variadic_parameter,
),
parameters: ($) => seq("(", optional(trailCommaSep1($._parameters)), ")"),
_return_type: ($) => seq("->", field("return_type", $.type)),
function_definition: ($) =>
seq(
optional($.annotations),
optional(choice($.static_keyword, $.remote_keyword)),
"func",
optional(field("name", $.name)),
field("parameters", $.parameters),
optional($._return_type),
// body is optional to support abstract function definitions. Without
// body, there must be a newline or body_end. _return_type with generic
// parameters without a newline or body_end will erroneously parse
// any following lines.
choice(seq(":", field("body", $.body)), $._newline, $._body_end),
),
lambda: ($) =>
seq(
"func",
optional(field("name", $.name)),
field("parameters", $.parameters),
optional($._return_type),
":",
field("body", $.body),
),
constructor_definition: ($) =>
seq(
"func",
"_init",
field("parameters", $.parameters),
optional(seq(".", field("arguments", $.arguments))),
optional($._return_type),
":",
field("body", $.body),
),
// -----------------------------------------------------------------------------
// - Function Call -
// -----------------------------------------------------------------------------
arguments: ($) =>
seq("(", optional(trailCommaSep1($._rhs_expression)), ")"),
base_call: ($) =>
prec(PREC.call, seq(".", $.identifier, field("arguments", $.arguments))),
call: ($) =>
prec(
PREC.call,
seq($._primary_expression, field("arguments", $.arguments)),
),
// This rule is for trailing backslashes to indicate line continuation. We
// capture those as anonymous '\' tokens to be able to preserve them in code
// formatters.
line_continuation: ($) => token(seq("\\", /\r?\n/)),
}, // end rules
});
function sep1(rule, separator) {
return seq(rule, repeat(seq(separator, rule)));
}
function trailSep1(rule, sep) {
return seq(sep1(rule, sep), optional(sep));
}
function commaSep1(rule) {
return sep1(rule, ",");
}
function trailCommaSep1(rule) {
return trailSep1(rule, ",");
}

@ -0,0 +1,399 @@
{
"name": "tree-sitter-gdscript",
"version": "6.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tree-sitter-gdscript",
"version": "6.0.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-addon-api": "^8.3.1",
"node-gyp-build": "^4.8.4"
},
"devDependencies": {
"prebuildify": "^6.0.1",
"prettier": "^3.6.2",
"tree-sitter-cli": "^0.24.7"
},
"peerDependencies": {
"tree-sitter": "^0.21.1"
},
"peerDependenciesMeta": {
"tree-sitter": {
"optional": true
}
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"dev": true,
"license": "ISC"
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true,
"license": "MIT"
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC"
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"dev": true,
"license": "MIT"
},
"node_modules/node-abi": {
"version": "3.75.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz",
"integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-addon-api": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz",
"integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/npm-run-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz",
"integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/prebuildify": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz",
"integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.5",
"mkdirp-classic": "^0.5.3",
"node-abi": "^3.3.0",
"npm-run-path": "^3.1.0",
"pump": "^3.0.0",
"tar-fs": "^2.1.0"
},
"bin": {
"prebuildify": "bin.js"
}
},
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pump": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
"integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
"dev": true,
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tree-sitter": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz",
"integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"node-addon-api": "^8.0.0",
"node-gyp-build": "^4.8.0"
}
},
"node_modules/tree-sitter-cli": {
"version": "0.24.7",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.24.7.tgz",
"integrity": "sha512-o4gnE82pVmMMhJbWwD6+I9yr4lXii5Ci5qEQ2pFpUbVy1YiD8cizTJaqdcznA0qEbo7l2OneI1GocChPrI4YGQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"tree-sitter": "cli.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
}
}
}

@ -0,0 +1,59 @@
{
"name": "tree-sitter-gdscript",
"version": "6.0.0",
"description": "Grammar for Godot's built-in scripting language.",
"repository": {
"type": "git",
"url": "git+https://github.com/prestonknopp/tree-sitter-gdscript.git"
},
"license": "MIT",
"author": "Preston Knopp",
"main": "bindings/node",
"keywords": [
"incremental",
"parsing",
"tree-sitter",
"gdscript",
"godot"
],
"files": [
"grammar.js",
"tree-sitter.json",
"binding.gyp",
"prebuilds/**",
"bindings/node/*",
"queries/*",
"src/**",
"*.wasm"
],
"dependencies": {
"node-addon-api": "^8.3.1",
"node-gyp-build": "^4.8.4"
},
"devDependencies": {
"prebuildify": "^6.0.1",
"prettier": "^3.6.2",
"tree-sitter-cli": "^0.24.7"
},
"peerDependencies": {
"tree-sitter": "^0.21.1"
},
"peerDependenciesMeta": {
"tree-sitter": {
"optional": true
}
},
"scripts": {
"install": "node-gyp-build",
"prebuild": "prebuildify --strip -t electron@34.5.5 -t electron@35.3.0 -t electron@36.2.0 -t v20.19.1 -t v22.15.0 -t v23.11.0 -t v24.0.1",
"prestart": "tree-sitter build --wasm",
"start": "tree-sitter playground",
"versions": "node --version && npm --version && tree-sitter --version",
"test": "tree-sitter test && node --test bindings/node/*_test.js",
"generate": "tree-sitter generate",
"genTest": "tree-sitter generate && npm test",
"parse": "tree-sitter parse",
"ts": "tree-sitter",
"format": "prettier -w grammar.js package.json"
}
}

@ -0,0 +1,29 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "tree-sitter-gdscript"
description = "Grammar for Godot's built-in scripting language."
version = "6.0.0"
keywords = ["incremental", "parsing", "tree-sitter", "gdscript"]
classifiers = [
"Intended Audience :: Developers",
"Topic :: Software Development :: Compilers",
"Topic :: Text Processing :: Linguistic",
"Typing :: Typed",
]
authors = [{ name = "Preston Knopp", email = "prestonknopp@gmail.com" }]
requires-python = ">=3.9"
license.text = "MIT"
readme = "README.md"
[project.urls]
Homepage = "https://github.com/prestonknopp/tree-sitter-gdscript"
[project.optional-dependencies]
core = ["tree-sitter~=0.22"]
[tool.cibuildwheel]
build = "cp39-*"
build-frontend = "build"

@ -0,0 +1,62 @@
from os.path import isdir, join
from platform import system
from setuptools import Extension, find_packages, setup
from setuptools.command.build import build
from wheel.bdist_wheel import bdist_wheel
class Build(build):
def run(self):
if isdir("queries"):
dest = join(self.build_lib, "tree_sitter_gdscript", "queries")
self.copy_tree("queries", dest)
super().run()
class BdistWheel(bdist_wheel):
def get_tag(self):
python, abi, platform = super().get_tag()
if python.startswith("cp"):
python, abi = "cp39", "abi3"
return python, abi, platform
setup(
packages=find_packages("bindings/python"),
package_dir={"": "bindings/python"},
package_data={
"tree_sitter_gdscript": ["*.pyi", "py.typed"],
"tree_sitter_gdscript.queries": ["*.scm"],
},
ext_package="tree_sitter_gdscript",
ext_modules=[
Extension(
name="_binding",
sources=[
"bindings/python/tree_sitter_gdscript/binding.c",
"src/parser.c",
"src/scanner.c",
],
extra_compile_args=[
"-std=c11",
"-fvisibility=hidden",
] if system() != "Windows" else [
"/std:c11",
"/utf-8",
],
define_macros=[
("Py_LIMITED_API", "0x03090000"),
("PY_SSIZE_T_CLEAN", None),
("TREE_SITTER_HIDE_SYMBOLS", None),
],
include_dirs=["src"],
py_limited_api=True,
)
],
cmdclass={
"build": Build,
"bdist_wheel": BdistWheel
},
zip_safe=False
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,554 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "tree_sitter/parser.h"
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define VEC_RESIZE(vec, _cap) \
void *tmp = realloc((vec)->data, (_cap) * sizeof((vec)->data[0])); \
assert(tmp != NULL); \
(vec)->data = tmp; \
(vec)->cap = (_cap);
#define VEC_GROW(vec, _cap) \
if ((vec)->cap < (_cap)) { \
VEC_RESIZE((vec), (_cap)); \
}
#define VEC_PUSH(vec, el) \
if ((vec)->cap == (vec)->len) { \
VEC_RESIZE((vec), MAX(16, (vec)->len * 2)); \
} \
(vec)->data[(vec)->len++] = (el);
#define VEC_POP(vec) (vec)->len--;
#define VEC_NEW \
{ .len = 0, .cap = 0, .data = NULL }
#define VEC_BACK(vec) ((vec)->data[(vec)->len - 1])
#define VEC_FREE(vec) \
{ \
if ((vec)->data != NULL) \
free((vec)->data); \
}
#define VEC_CLEAR(vec) \
{ (vec)->len = 0; }
enum TokenType {
NEWLINE,
INDENT,
DEDENT,
STRING_START,
STRING_CONTENT,
STRING_END,
STRING_NAME_START,
NODE_PATH_START,
CLOSE_PAREN,
CLOSE_BRACKET,
CLOSE_BRACE,
COMMA,
/* COLON, // See grammar.js externals */
BODY_END,
};
typedef enum {
SingleQuote = 1 << 0,
DoubleQuote = 1 << 1,
Triple = 1 << 2,
Raw = 1 << 3,
Name = 1 << 4,
NodePath = 1 << 5,
} Flags;
typedef struct {
char flags;
} Delimiter;
static inline Delimiter new_delimiter() { return (Delimiter){0}; }
static inline bool is_triple(Delimiter *delimiter) {
return delimiter->flags & Triple;
}
static inline bool is_raw(Delimiter *delimiter) {
return delimiter->flags & Raw;
}
static inline bool is_name(Delimiter *delimiter) {
return delimiter->flags & Name;
}
static inline bool is_node_path(Delimiter *delimiter) {
return delimiter->flags & NodePath;
}
static inline int32_t end_character(Delimiter *delimiter) {
if (delimiter->flags & SingleQuote) {
return '\'';
}
if (delimiter->flags & DoubleQuote) {
return '"';
}
return 0;
}
static inline void set_triple(Delimiter *delimiter) {
delimiter->flags |= Triple;
}
static inline void set_raw(Delimiter *delimiter) {
delimiter->flags |= Raw;
}
static inline void set_name(Delimiter *delimiter) {
delimiter->flags |= Name;
}
static inline void set_node_path(Delimiter *delimiter) {
delimiter->flags |= NodePath;
}
static inline void set_end_character(Delimiter *delimiter, int32_t character) {
switch (character) {
case '\'':
delimiter->flags |= SingleQuote;
break;
case '"':
delimiter->flags |= DoubleQuote;
break;
default:
assert(false);
}
}
static inline const char *delimiter_string(Delimiter *delimiter) {
if (delimiter->flags & SingleQuote) {
return "\'";
}
if (delimiter->flags & DoubleQuote) {
return "\"";
}
return "";
}
typedef struct {
uint32_t len;
uint32_t cap;
uint16_t *data;
} indent_vec;
typedef struct {
uint32_t len;
uint32_t cap;
Delimiter *data;
} delimiter_vec;
typedef struct {
indent_vec *indents;
delimiter_vec *delimiters;
} Scanner;
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
/**
* Skip whitespace characters and update indentation tracking.
*
* @param lexer The lexer to advance
* @param indent_length Pointer to current indentation length (modified in place)
* @param found_end_of_line Pointer to end-of-line flag (set to true if newline encountered), can be NULL
* @return true if a whitespace character was processed, false otherwise
*
* Handles:
* - ' ' (space): increments indent_length
* - '\t' (tab): adds 8 to indent_length
* - '\n' (newline): resets indent_length to 0, sets found_end_of_line to true (if not NULL)
* - '\r', '\f' (carriage return, form feed): resets indent_length to 0
*/
static inline bool skip_whitespace(TSLexer *lexer, uint32_t *indent_length, bool *found_end_of_line) {
if (lexer->lookahead == '\n') {
if (found_end_of_line) {
*found_end_of_line = true;
}
*indent_length = 0;
skip(lexer);
return true;
} else if (lexer->lookahead == ' ') {
(*indent_length)++;
skip(lexer);
return true;
} else if (lexer->lookahead == '\r' || lexer->lookahead == '\f') {
*indent_length = 0;
skip(lexer);
return true;
} else if (lexer->lookahead == '\t') {
*indent_length += 8;
skip(lexer);
return true;
}
return false;
}
static inline void handle_quote(TSLexer *lexer, Delimiter *delimiter, char quote) {
set_end_character(delimiter, quote);
advance(lexer);
lexer->mark_end(lexer);
if (lexer->lookahead == quote) {
advance(lexer);
if (lexer->lookahead == quote) {
advance(lexer);
lexer->mark_end(lexer);
set_triple(delimiter);
}
}
}
bool tree_sitter_gdscript_external_scanner_scan(void *payload, TSLexer *lexer,
const bool *valid_symbols) {
Scanner *scanner = (Scanner *)payload;
bool error_recovery_mode =
valid_symbols[STRING_CONTENT] && valid_symbols[INDENT];
bool within_brackets = valid_symbols[CLOSE_BRACE] ||
valid_symbols[CLOSE_PAREN] ||
valid_symbols[CLOSE_BRACKET];
if (valid_symbols[STRING_CONTENT] && scanner->delimiters->len > 0 &&
!error_recovery_mode) {
Delimiter delimiter = VEC_BACK(scanner->delimiters);
int32_t end_char = end_character(&delimiter);
bool has_content = false;
while (lexer->lookahead) {
if (lexer->lookahead == '\\') {
if (is_raw(&delimiter)) {
// Step over the backslash.
lexer->advance(lexer, false);
// Step over any escaped quotes.
if (lexer->lookahead == end_character(&delimiter) ||
lexer->lookahead == '\\') {
lexer->advance(lexer, false);
}
continue;
} else {
lexer->mark_end(lexer);
lexer->result_symbol = STRING_CONTENT;
return has_content;
}
} else if (lexer->lookahead == end_char) {
if (is_triple(&delimiter)) {
lexer->mark_end(lexer);
lexer->advance(lexer, false);
if (lexer->lookahead == end_char) {
lexer->advance(lexer, false);
if (lexer->lookahead == end_char) {
if (has_content) {
lexer->result_symbol = STRING_CONTENT;
} else {
lexer->advance(lexer, false);
lexer->mark_end(lexer);
VEC_POP(scanner->delimiters);
lexer->result_symbol = STRING_END;
}
return true;
}
lexer->mark_end(lexer);
lexer->result_symbol = STRING_CONTENT;
return true;
}
lexer->mark_end(lexer);
lexer->result_symbol = STRING_CONTENT;
return true;
}
if (has_content) {
lexer->result_symbol = STRING_CONTENT;
} else {
lexer->advance(lexer, false);
VEC_POP(scanner->delimiters);
lexer->result_symbol = STRING_END;
}
lexer->mark_end(lexer);
return true;
}
advance(lexer);
has_content = true;
}
}
lexer->mark_end(lexer);
bool found_end_of_line = false;
uint32_t indent_length = 0;
// Track the indentation level of the most recent line that contained actual content
// (comments or code statements). This is used to prevent premature DEDENT emission
// when empty lines appear between content at the same indentation level, and to
// maintain proper scope association for comments that appear at the end of blocks.
uint32_t last_non_empty_indent = 0;
for (;;) {
if (skip_whitespace(lexer, &indent_length, &found_end_of_line)) {
continue;
} else if (lexer->lookahead == '#') {
// The current scanner can scan past a line return into a comment.
// In that case we want to stop processing here, since it means
// we're looking potentially at a comment on the next line compared
// to the starting point of this scan.
if (!found_end_of_line) {
break;
}
last_non_empty_indent = indent_length;
// Consume the entire comment line
while (lexer->lookahead && lexer->lookahead != '\n') {
skip(lexer);
}
if (lexer->lookahead == '\n') {
skip(lexer);
indent_length = 0;
}
} else if (lexer->lookahead == '\\') {
skip(lexer);
if (lexer->lookahead == '\r') {
skip(lexer);
}
if (lexer->lookahead == '\n' || lexer->eof(lexer)) {
skip(lexer);
} else {
return false;
}
} else if (lexer->eof(lexer)) {
// At EOF, use the last non-empty line's indentation if we haven't seen content
// this prevents dedenting to 0 at EOF if the last line is empty
if (last_non_empty_indent > 0) {
indent_length = last_non_empty_indent;
}
if (scanner->indents->len > 0) {
uint16_t current_indent_length = VEC_BACK(scanner->indents);
if (indent_length != current_indent_length) {
indent_length = 0;
}
} else {
indent_length = 0;
}
found_end_of_line = true;
break;
} else if (lexer->lookahead == '\n') {
// Empty line, skip it and continue
skip(lexer);
indent_length = 0;
} else {
if (indent_length == 0 && last_non_empty_indent > 0) {
// We're at content at level 0, but we had non-empty content at a higher level
// Check if we should defer DEDENT
if (scanner->indents->len > 0) {
uint16_t current_indent_length = VEC_BACK(scanner->indents);
if (last_non_empty_indent == current_indent_length ) {
// We had comments at the current indent level, don't dedent immediately
return false;
}
}
}
break;
}
}
if (found_end_of_line) {
if (scanner->indents->len > 0) {
uint16_t current_indent_length = VEC_BACK(scanner->indents);
if (valid_symbols[INDENT] &&
indent_length > current_indent_length) {
VEC_PUSH(scanner->indents, indent_length);
lexer->result_symbol = INDENT;
return true;
}
if (valid_symbols[DEDENT] && indent_length < current_indent_length) {
VEC_POP(scanner->indents);
lexer->result_symbol = DEDENT;
return true;
}
}
if (valid_symbols[NEWLINE] && !error_recovery_mode) {
lexer->result_symbol = NEWLINE;
return true;
}
}
// This if statement can be placed before the above if statement that
// handles newlines. However, it feels safer to give indentation and
// newlines higher precedence.
if (
// Guard against BODY_END tokens overriding valid tokens.
!valid_symbols[COMMA] &&
/* !valid_symbols[COLON] && */
!valid_symbols[CLOSE_PAREN] && !valid_symbols[CLOSE_BRACE] &&
!valid_symbols[CLOSE_BRACKET] &&
// Body ends occur in error recovery mode since the grammar does not
// (cannot?) specify that a body can end with the below characters
// without consuming them itself.
(error_recovery_mode || valid_symbols[BODY_END])) {
if (lexer->lookahead == ',' || // separator
lexer->lookahead == ')' || // args, params, paren expr
lexer->lookahead == '}' || // dictionary (may not be needed)
lexer->lookahead == ']' // array
/* lexer->lookahead == ':' // key-value pairs (breaks if
elses) */
) {
// BODY_END tokens can take the place of a dedent. Therefore, we
// should pop the stack when DEDENT is valid.
if (valid_symbols[DEDENT] && scanner->indents->len > 0) {
VEC_POP(scanner->indents);
}
lexer->result_symbol = BODY_END;
return true;
}
}
if (valid_symbols[STRING_START] ||
valid_symbols[STRING_NAME_START] ||
valid_symbols[NODE_PATH_START]) {
Delimiter delimiter = new_delimiter();
bool has_flags = true;
switch (lexer->lookahead) {
case 'r': set_raw(&delimiter); break;
case '&': set_name(&delimiter); break;
case '^': set_node_path(&delimiter); break;
// For backward compatibility with 3.x versions
case '@': set_node_path(&delimiter); break;
default: has_flags = false; break;
}
if (has_flags) advance(lexer);
if (lexer->lookahead == '\'' || lexer->lookahead == '"') {
handle_quote(lexer, &delimiter, lexer->lookahead);
}
if (end_character(&delimiter)) {
VEC_PUSH(scanner->delimiters, delimiter);
if (is_node_path(&delimiter)) {
lexer->result_symbol = NODE_PATH_START;
} else if (is_name(&delimiter)) {
lexer->result_symbol = STRING_NAME_START;
} else {
lexer->result_symbol = STRING_START;
}
return true;
}
if (has_flags) {
return false;
}
}
return false;
}
unsigned tree_sitter_gdscript_external_scanner_serialize(void *payload,
char *buffer) {
Scanner *scanner = (Scanner *)payload;
size_t size = 0;
size_t delimiter_count = scanner->delimiters->len;
if (delimiter_count > UINT8_MAX) {
delimiter_count = UINT8_MAX;
}
buffer[size++] = (char)delimiter_count;
if (delimiter_count > 0) {
memcpy(&buffer[size], scanner->delimiters->data, delimiter_count);
}
size += delimiter_count;
for (int iter = 1; (uint32_t)iter < scanner->indents->len &&
size < TREE_SITTER_SERIALIZATION_BUFFER_SIZE;
++iter) {
buffer[size++] = (char)scanner->indents->data[iter];
}
return size;
}
void tree_sitter_gdscript_external_scanner_deserialize(void *payload,
const char *buffer,
unsigned length) {
Scanner *scanner = (Scanner *)payload;
VEC_CLEAR(scanner->delimiters);
VEC_CLEAR(scanner->indents);
VEC_PUSH(scanner->indents, 0);
if (length > 0) {
size_t size = 0;
size_t delimiter_count = (uint8_t)buffer[size++];
if (delimiter_count > 0) {
VEC_GROW(scanner->delimiters, delimiter_count);
scanner->delimiters->len = delimiter_count;
memcpy(scanner->delimiters->data, &buffer[size], delimiter_count);
size += delimiter_count;
}
// Deserialize the indents
for (; size < length; size++) {
VEC_PUSH(scanner->indents, (unsigned char)buffer[size]);
}
assert(size == length);
}
}
void *tree_sitter_gdscript_external_scanner_create() {
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
_Static_assert(sizeof(Delimiter) == sizeof(char), "");
#else
assert(sizeof(Delimiter) == sizeof(char));
#endif
Scanner *scanner = calloc(1, sizeof(Scanner));
if (!scanner) {
// What is the tree-sitter idiomatic way to handle this?
// fprintf(stderr, "Failed to allocate memory for scanner\n");
return NULL;
}
scanner->indents = calloc(1, sizeof(indent_vec));
scanner->delimiters = calloc(1, sizeof(delimiter_vec));
tree_sitter_gdscript_external_scanner_deserialize(scanner, NULL, 0);
return scanner;
}
void tree_sitter_gdscript_external_scanner_destroy(void *payload) {
Scanner *scanner = (Scanner *)payload;
VEC_FREE(scanner->indents);
VEC_FREE(scanner->delimiters);
free(scanner->indents);
free(scanner->delimiters);
free(scanner);
}

@ -0,0 +1,54 @@
#ifndef TREE_SITTER_ALLOC_H_
#define TREE_SITTER_ALLOC_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
// Allow clients to override allocation functions
#ifdef TREE_SITTER_REUSE_ALLOCATOR
extern void *(*ts_current_malloc)(size_t size);
extern void *(*ts_current_calloc)(size_t count, size_t size);
extern void *(*ts_current_realloc)(void *ptr, size_t size);
extern void (*ts_current_free)(void *ptr);
#ifndef ts_malloc
#define ts_malloc ts_current_malloc
#endif
#ifndef ts_calloc
#define ts_calloc ts_current_calloc
#endif
#ifndef ts_realloc
#define ts_realloc ts_current_realloc
#endif
#ifndef ts_free
#define ts_free ts_current_free
#endif
#else
#ifndef ts_malloc
#define ts_malloc malloc
#endif
#ifndef ts_calloc
#define ts_calloc calloc
#endif
#ifndef ts_realloc
#define ts_realloc realloc
#endif
#ifndef ts_free
#define ts_free free
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ALLOC_H_

@ -0,0 +1,291 @@
#ifndef TREE_SITTER_ARRAY_H_
#define TREE_SITTER_ARRAY_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "./alloc.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4101)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
#define Array(T) \
struct { \
T *contents; \
uint32_t size; \
uint32_t capacity; \
}
/// Initialize an array.
#define array_init(self) \
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
/// Create an empty array.
#define array_new() \
{ NULL, 0, 0 }
/// Get a pointer to the element at a given `index` in the array.
#define array_get(self, _index) \
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
/// Get a pointer to the first element in the array.
#define array_front(self) array_get(self, 0)
/// Get a pointer to the last element in the array.
#define array_back(self) array_get(self, (self)->size - 1)
/// Clear the array, setting its size to zero. Note that this does not free any
/// memory allocated for the array's contents.
#define array_clear(self) ((self)->size = 0)
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
/// less than the array's current capacity, this function has no effect.
#define array_reserve(self, new_capacity) \
_array__reserve((Array *)(self), array_elem_size(self), new_capacity)
/// Free any memory allocated for this array. Note that this does not free any
/// memory allocated for the array's contents.
#define array_delete(self) _array__delete((Array *)(self))
/// Push a new `element` onto the end of the array.
#define array_push(self, element) \
(_array__grow((Array *)(self), 1, array_elem_size(self)), \
(self)->contents[(self)->size++] = (element))
/// Increase the array's size by `count` elements.
/// New elements are zero-initialized.
#define array_grow_by(self, count) \
do { \
if ((count) == 0) break; \
_array__grow((Array *)(self), count, array_elem_size(self)); \
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
(self)->size += (count); \
} while (0)
/// Append all elements from one array to the end of another.
#define array_push_all(self, other) \
array_extend((self), (other)->size, (other)->contents)
/// Append `count` elements to the end of the array, reading their values from the
/// `contents` pointer.
#define array_extend(self, count, contents) \
_array__splice( \
(Array *)(self), array_elem_size(self), (self)->size, \
0, count, contents \
)
/// Remove `old_count` elements from the array starting at the given `index`. At
/// the same index, insert `new_count` new elements, reading their values from the
/// `new_contents` pointer.
#define array_splice(self, _index, old_count, new_count, new_contents) \
_array__splice( \
(Array *)(self), array_elem_size(self), _index, \
old_count, new_count, new_contents \
)
/// Insert one `element` into the array at the given `index`.
#define array_insert(self, _index, element) \
_array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
/// Remove one element from the array at the given `index`.
#define array_erase(self, _index) \
_array__erase((Array *)(self), array_elem_size(self), _index)
/// Pop the last element off the array, returning the element by value.
#define array_pop(self) ((self)->contents[--(self)->size])
/// Assign the contents of one array to another, reallocating if necessary.
#define array_assign(self, other) \
_array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
/// Swap one array with another
#define array_swap(self, other) \
_array__swap((Array *)(self), (Array *)(other))
/// Get the size of the array contents
#define array_elem_size(self) (sizeof *(self)->contents)
/// Search a sorted array for a given `needle` value, using the given `compare`
/// callback to determine the order.
///
/// If an existing element is found to be equal to `needle`, then the `index`
/// out-parameter is set to the existing value's index, and the `exists`
/// out-parameter is set to true. Otherwise, `index` is set to an index where
/// `needle` should be inserted in order to preserve the sorting, and `exists`
/// is set to false.
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
/// Search a sorted array for a given `needle` value, using integer comparisons
/// of a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_with`.
#define array_search_sorted_by(self, field, needle, _index, _exists) \
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
/// Insert a given `value` into a sorted array, using the given `compare`
/// callback to determine the order.
#define array_insert_sorted_with(self, compare, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
/// Insert a given `value` into a sorted array, using integer comparisons of
/// a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_by`.
#define array_insert_sorted_by(self, field, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
// Private
typedef Array(void) Array;
/// This is not what you're looking for, see `array_delete`.
static inline void _array__delete(Array *self) {
if (self->contents) {
ts_free(self->contents);
self->contents = NULL;
self->size = 0;
self->capacity = 0;
}
}
/// This is not what you're looking for, see `array_erase`.
static inline void _array__erase(Array *self, size_t element_size,
uint32_t index) {
assert(index < self->size);
char *contents = (char *)self->contents;
memmove(contents + index * element_size, contents + (index + 1) * element_size,
(self->size - index - 1) * element_size);
self->size--;
}
/// This is not what you're looking for, see `array_reserve`.
static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
if (new_capacity > self->capacity) {
if (self->contents) {
self->contents = ts_realloc(self->contents, new_capacity * element_size);
} else {
self->contents = ts_malloc(new_capacity * element_size);
}
self->capacity = new_capacity;
}
}
/// This is not what you're looking for, see `array_assign`.
static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
_array__reserve(self, element_size, other->size);
self->size = other->size;
memcpy(self->contents, other->contents, self->size * element_size);
}
/// This is not what you're looking for, see `array_swap`.
static inline void _array__swap(Array *self, Array *other) {
Array swap = *other;
*other = *self;
*self = swap;
}
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
uint32_t new_size = self->size + count;
if (new_size > self->capacity) {
uint32_t new_capacity = self->capacity * 2;
if (new_capacity < 8) new_capacity = 8;
if (new_capacity < new_size) new_capacity = new_size;
_array__reserve(self, element_size, new_capacity);
}
}
/// This is not what you're looking for, see `array_splice`.
static inline void _array__splice(Array *self, size_t element_size,
uint32_t index, uint32_t old_count,
uint32_t new_count, const void *elements) {
uint32_t new_size = self->size + new_count - old_count;
uint32_t old_end = index + old_count;
uint32_t new_end = index + new_count;
assert(old_end <= self->size);
_array__reserve(self, element_size, new_size);
char *contents = (char *)self->contents;
if (self->size > old_end) {
memmove(
contents + new_end * element_size,
contents + old_end * element_size,
(self->size - old_end) * element_size
);
}
if (new_count > 0) {
if (elements) {
memcpy(
(contents + index * element_size),
elements,
new_count * element_size
);
} else {
memset(
(contents + index * element_size),
0,
new_count * element_size
);
}
}
self->size += new_count - old_count;
}
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
do { \
*(_index) = start; \
*(_exists) = false; \
uint32_t size = (self)->size - *(_index); \
if (size == 0) break; \
int comparison; \
while (size > 1) { \
uint32_t half_size = size / 2; \
uint32_t mid_index = *(_index) + half_size; \
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
if (comparison <= 0) *(_index) = mid_index; \
size -= half_size; \
} \
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
if (comparison == 0) *(_exists) = true; \
else if (comparison < 0) *(_index) += 1; \
} while (0)
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
/// parameter by reference in order to work with the generic sorting function above.
#define _compare_int(a, b) ((int)*(a) - (int)(b))
#ifdef _MSC_VER
#pragma warning(pop)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ARRAY_H_

@ -0,0 +1,266 @@
#ifndef TREE_SITTER_PARSER_H_
#define TREE_SITTER_PARSER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define ts_builtin_sym_error ((TSSymbol)-1)
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSStateId;
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
#endif
typedef struct {
TSFieldId field_id;
uint8_t child_index;
bool inherited;
} TSFieldMapEntry;
typedef struct {
uint16_t index;
uint16_t length;
} TSFieldMapSlice;
typedef struct {
bool visible;
bool named;
bool supertype;
} TSSymbolMetadata;
typedef struct TSLexer TSLexer;
struct TSLexer {
int32_t lookahead;
TSSymbol result_symbol;
void (*advance)(TSLexer *, bool);
void (*mark_end)(TSLexer *);
uint32_t (*get_column)(TSLexer *);
bool (*is_at_included_range_start)(const TSLexer *);
bool (*eof)(const TSLexer *);
void (*log)(const TSLexer *, const char *, ...);
};
typedef enum {
TSParseActionTypeShift,
TSParseActionTypeReduce,
TSParseActionTypeAccept,
TSParseActionTypeRecover,
} TSParseActionType;
typedef union {
struct {
uint8_t type;
TSStateId state;
bool extra;
bool repetition;
} shift;
struct {
uint8_t type;
uint8_t child_count;
TSSymbol symbol;
int16_t dynamic_precedence;
uint16_t production_id;
} reduce;
uint8_t type;
} TSParseAction;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
} TSLexMode;
typedef union {
TSParseAction action;
struct {
uint8_t count;
bool reusable;
} entry;
} TSParseActionEntry;
typedef struct {
int32_t start;
int32_t end;
} TSCharacterRange;
struct TSLanguage {
uint32_t version;
uint32_t symbol_count;
uint32_t alias_count;
uint32_t token_count;
uint32_t external_token_count;
uint32_t state_count;
uint32_t large_state_count;
uint32_t production_id_count;
uint32_t field_count;
uint16_t max_alias_sequence_length;
const uint16_t *parse_table;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSParseActionEntry *parse_actions;
const char * const *symbol_names;
const char * const *field_names;
const TSFieldMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const TSSymbolMetadata *symbol_metadata;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
const TSSymbol *alias_sequences;
const TSLexMode *lex_modes;
bool (*lex_fn)(TSLexer *, TSStateId);
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
TSSymbol keyword_capture_token;
struct {
const bool *states;
const TSSymbol *symbol_map;
void *(*create)(void);
void (*destroy)(void *);
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
unsigned (*serialize)(void *, char *);
void (*deserialize)(void *, const char *, unsigned);
} external_scanner;
const TSStateId *primary_state_ids;
};
static inline bool set_contains(TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
uint32_t index = 0;
uint32_t size = len - index;
while (size > 1) {
uint32_t half_size = size / 2;
uint32_t mid_index = index + half_size;
TSCharacterRange *range = &ranges[mid_index];
if (lookahead >= range->start && lookahead <= range->end) {
return true;
} else if (lookahead > range->end) {
index = mid_index;
}
size -= half_size;
}
TSCharacterRange *range = &ranges[index];
return (lookahead >= range->start && lookahead <= range->end);
}
/*
* Lexer Macros
*/
#ifdef _MSC_VER
#define UNUSED __pragma(warning(suppress : 4101))
#else
#define UNUSED __attribute__((unused))
#endif
#define START_LEXER() \
bool result = false; \
bool skip = false; \
UNUSED \
bool eof = false; \
int32_t lookahead; \
goto start; \
next_state: \
lexer->advance(lexer, skip); \
start: \
skip = false; \
lookahead = lexer->lookahead;
#define ADVANCE(state_value) \
{ \
state = state_value; \
goto next_state; \
}
#define ADVANCE_MAP(...) \
{ \
static const uint16_t map[] = { __VA_ARGS__ }; \
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
if (map[i] == lookahead) { \
state = map[i + 1]; \
goto next_state; \
} \
} \
}
#define SKIP(state_value) \
{ \
skip = true; \
state = state_value; \
goto next_state; \
}
#define ACCEPT_TOKEN(symbol_value) \
result = true; \
lexer->result_symbol = symbol_value; \
lexer->mark_end(lexer);
#define END_STATE() return result;
/*
* Parse Table Macros
*/
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
#define STATE(id) id
#define ACTIONS(id) id
#define SHIFT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value) \
} \
}}
#define SHIFT_REPEAT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value), \
.repetition = true \
} \
}}
#define SHIFT_EXTRA() \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.extra = true \
} \
}}
#define REDUCE(symbol_name, children, precedence, prod_id) \
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_name, \
.child_count = children, \
.dynamic_precedence = precedence, \
.production_id = prod_id \
}, \
}}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_PARSER_H_

@ -0,0 +1,191 @@
=====================================
Abstract class_name statement
=====================================
@abstract class_name BaseClass
---
(source
(class_name_statement
(annotations
(annotation (identifier)))
(name)))
=====================================
Abstract class definition
=====================================
@abstract class TestClass:
pass
---
(source
(class_definition
(annotations
(annotation (identifier)))
(name)
(class_body
(pass_statement))))
=====================================
Abstract class with extends
=====================================
@abstract class TestClass extends BaseClass:
pass
---
(source
(class_definition
(annotations
(annotation (identifier)))
(name)
(extends_statement (type (identifier)))
(class_body
(pass_statement))))
=====================================
Abstract function declaration
=====================================
@abstract func test_func()
---
(source
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters)))
=====================================
Abstract function with parameters
=====================================
@abstract func abstract_with_params(param1: String, param2: int)
---
(source
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters
(typed_parameter (identifier) (type (identifier)))
(typed_parameter (identifier) (type (identifier))))))
=====================================
Abstract function with return type
=====================================
@abstract func abstract_with_return_type() -> String
---
(source
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters)
(type (identifier))))
=====================================
Abstract function with parameters and return type
=====================================
@abstract func abstract_with_params_and_return(input: String) -> int
---
(source
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters
(typed_parameter (identifier) (type (identifier))))
(type (identifier))))
=====================================
Mixed abstract and concrete methods in class
=====================================
@abstract class TestClass:
@abstract func test_func()
func concrete_method():
pass
---
(source
(class_definition
(annotations
(annotation (identifier)))
(name)
(class_body
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters))
(function_definition
(name)
(parameters)
(body
(pass_statement))))))
=====================================
Standalone abstract and concrete functions
=====================================
@abstract func simple_abstract()
@abstract func abstract_with_params(param1: String, param2: int)
@abstract func abstract_with_return_type() -> String
@abstract func abstract_with_params_and_return(input: String) -> int
func concrete_method():
pass
---
(source
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters))
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters
(typed_parameter (identifier) (type (identifier)))
(typed_parameter (identifier) (type (identifier)))))
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters)
(type (identifier)))
(function_definition
(annotations
(annotation (identifier)))
(name)
(parameters
(typed_parameter (identifier) (type (identifier))))
(type (identifier)))
(function_definition
(name)
(parameters)
(body
(pass_statement))))

@ -0,0 +1,306 @@
=====================================
Inline Comments in Set and Get Blocks
=====================================
var prop = 10: # var comment
set(value): # set comment
prop = value
get: # get comment
return prop
---
(source
(variable_statement
name: (name)
value: (integer)
setget: (setget
(comment)
set: (set_body
(parameters
(identifier))
(comment)
body: (body
(expression_statement
(assignment
left: (identifier)
right: (identifier)))))
get: (get_body
(comment)
body: (body
(return_statement
(identifier)))))))
=====================================
Inline Comments in Class and Function Definitions
=====================================
class InnerClass: # class comment
pass
func _init(): # constructor comment
pass
func foo(): # func comment
pass
---
(source
(class_definition
(name)
(comment)
(class_body
(pass_statement)))
(constructor_definition
(parameters)
(comment)
(body
(pass_statement)))
(function_definition
(name)
(parameters)
(comment)
(body
(pass_statement))))
=====================================
Inline Comments in If/Elif/Else
=====================================
func test():
if true: # if comment
pass
elif false: # elif comment
pass
else: # else comment
pass
---
(source
(function_definition
(name)
(parameters)
(body
(if_statement
(true)
(comment)
(body
(pass_statement))
(elif_clause
(false)
(comment)
(body
(pass_statement)))
(else_clause
(comment)
(body
(pass_statement)))))))
=====================================
Inline Comments in Match Block
=====================================
func test():
match 0: # match comment
1: # case comment
pass
_: # default comment
pass
---
(source
(function_definition
(name)
(parameters)
(body
(match_statement
(integer)
(comment)
(match_body
(pattern_section
(integer)
(comment)
(body
(pass_statement)))
(pattern_section
(identifier)
(comment)
(body
(pass_statement))))))))
=====================================
Inline Comments in For Loop
=====================================
func test():
for i in 10: # for comment
pass
---
(source
(function_definition
(name)
(parameters)
(body
(for_statement
(identifier)
(integer)
(comment)
(body
(pass_statement))))))
=====================================
Inline Comments in While Loop
=====================================
func test():
while false: # while comment
pass
---
(source
(function_definition
(name)
(parameters)
(body
(while_statement
(false)
(comment)
(body
(pass_statement))))))
=====================================
Inline Comments in Lambda
=====================================
func test():
var lam = func(): # lambda comment
pass
---
(source
(function_definition
(name)
(parameters)
(body
(variable_statement
(name)
(lambda
(parameters)
(comment)
(body
(pass_statement)))))))
=====================================
Inline Trailing Comments
=====================================
func test():
return # function trailing comment at end
---
(source
(function_definition
(name)
(parameters)
(body
(return_statement)
(comment))))
=====================================
Inline Trailing Comments After Class Definition
=====================================
class MyClass:
pass # class trailing comment
---
(source
(class_definition
(name)
(class_body
(pass_statement)
(comment))))
=====================================
Inline Comments After Control Flow
=====================================
func test():
for x in y:
continue
# Comment
pass
---
(source
(function_definition
(name)
(parameters)
(body
(for_statement
(identifier)
(identifier)
(body
(continue_statement)))
(comment)
(pass_statement))))
=====================================
Trailing Comments After Final Statement
=====================================
func test():
pass
# comment after final statement
func test2():
pass
# another comment after final statement
---
(source
(function_definition
(name)
(parameters)
(body
(pass_statement)
(comment)))
(function_definition
(name)
(parameters)
(body
(pass_statement)
(comment))))
=====================================
Comments: dedented comment in function body
=====================================
func test():
# properly indented comment
# improperly indented comment
pass
---
(source
(function_definition
(name)
(parameters)
(body
(comment)
(comment)
(pass_statement))))

@ -0,0 +1,20 @@
============================================
Annotations and keywords
============================================
@outside
@inside static func hello():
pass
---
(source
(annotation (identifier))
(function_definition
(annotations
(annotation (identifier)))
(static_keyword)
(name)
(parameters)
(body
(pass_statement))))

@ -0,0 +1,169 @@
============================================
Functions: Variadic parameters
============================================
func sum(first_number: float, ...numbers: Array) -> float:
return 0.0
---
(source
(function_definition
(name)
(parameters
(typed_parameter
(identifier)
(type
(identifier)))
(variadic_parameter
(typed_parameter
(identifier)
(type
(identifier)))))
(type
(identifier))
(body
(return_statement
(float)))))
============================================
Functions: Generic types in parameters and return types
============================================
func process_data(items: Array[String], mapping: Dictionary[int, String]) -> Dictionary[String, int]:
pass
func get_nodes() -> Array[Node]:
return []
func store_values(data: Dictionary[String, float]):
pass
---
(source
(function_definition
(name)
(parameters
(typed_parameter
(identifier)
(type
(subscript
(identifier)
(subscript_arguments
(identifier)))))
(typed_parameter
(identifier)
(type
(subscript
(identifier)
(subscript_arguments
(identifier)
(identifier))))))
(type
(subscript
(identifier)
(subscript_arguments
(identifier)
(identifier))))
(body
(pass_statement)))
(function_definition
(name)
(parameters)
(type
(subscript
(identifier)
(subscript_arguments
(identifier))))
(body
(return_statement
(array))))
(function_definition
(name)
(parameters
(typed_parameter
(identifier)
(type
(subscript
(identifier)
(subscript_arguments
(identifier)
(identifier))))))
(body
(pass_statement))))
============================================
Functions: Type inference in function parameters
============================================
func set_avatar_at(at := AvatarAt.LEFT) -> void:
pass
---
(source
(function_definition
name: (name)
parameters: (parameters
(typed_default_parameter
(identifier)
type: (inferred_type)
value: (attribute
(identifier)
(identifier))))
return_type: (type
(identifier))
body: (body
(pass_statement))))
============================================
Functions: Type inference in multiple function parameters
============================================
func test(x := 10, y := "hello"):
pass
---
(source
(function_definition
name: (name)
parameters: (parameters
(typed_default_parameter
(identifier)
type: (inferred_type)
value: (integer))
(typed_default_parameter
(identifier)
type: (inferred_type)
value: (string)))
body: (body
(pass_statement))))
============================================
Callables: Calling a callable named tool
============================================
var tool = Callable()
func test():
tool.call()
---
(source
(variable_statement
name: (name)
value: (call
(identifier)
arguments: (arguments)))
(function_definition
name: (name)
parameters: (parameters)
body: (body
(expression_statement
(attribute
(identifier)
(attribute_call
(identifier)
arguments: (arguments)))))))

@ -0,0 +1,104 @@
============================================
Inner Class: Test basic class with function
============================================
class Name:
func hello(): pass
---
(source
(class_definition (name)
(class_body
(function_definition (name) (parameters)
(body (pass_statement))))))
============================================
Inner Class: Test class with extends on one line and function
============================================
class Name extends Type:
func hello(): pass
---
(source
(class_definition (name) (extends_statement (type (identifier)))
(class_body
(function_definition (name) (parameters)
(body (pass_statement))))))
============================================
Inner Class: Test class with extends only
============================================
class Name:
extends Type
---
(source
(class_definition (name)
(class_body
(extends_statement (type (identifier))))))
============================================
Inner Class: Inner class with function definition on the same line
============================================
class C: func test(x):
print(x)
---
(source
(class_definition
name: (name)
body: (class_body
(function_definition
name: (name)
parameters: (parameters
(identifier))
body: (body
(expression_statement
(call
(identifier)
arguments: (arguments
(identifier)))))))))
============================================
Inner Class: Inner class with variable declaration on the same line
============================================
class MyClass: var x = 10
---
(source
(class_definition
name: (name)
body: (class_body
(variable_statement
name: (name)
value: (integer)))))
============================================
Inner Class: Inner class with static function definition on the same line
============================================
class Helper: static func utility():
return true
---
(source
(class_definition
name: (name)
body: (class_body
(function_definition
(static_keyword)
name: (name)
parameters: (parameters)
body: (body
(return_statement
(true)))))))

@ -0,0 +1,361 @@
============================================
#67 Parsing error when there are two or more consecutive semicolons
============================================
test() ; ; print()
class T:
var hello: T = "";; ;const c = 1;;;;;signal world
func hello():
pass;;pass
---
(source
(expression_statement
(call
(identifier)
(arguments)))
(expression_statement
(call
(identifier)
(arguments)))
(class_definition
(name)
(class_body
(variable_statement
(name)
(type
(identifier))
(string))
(const_statement
(name)
(integer))
(signal_statement
(name))
(function_definition
(name)
(parameters)
(body
(pass_statement)
(pass_statement))))))
============================================
#57 Parser can't handle certain multiline raw strings
============================================
r"hello"
r"hello
"
"
asdfasdfasf
"
print(r"hello
")
var a = "
asdfasdfasf
"
---
(source
(expression_statement (string))
(expression_statement (string))
(expression_statement (string))
(expression_statement
(call (identifier) (arguments (string))))
(variable_statement (name) (string)))
============================================
#56 Error when matching a dictionary without specifying values
============================================
match x:
{ "key", 1, 0xFF, "key": 100 }: pass
---
(source
(match_statement
(identifier)
(match_body
(pattern_section
(dictionary
(string)
(integer)
(integer)
(pair
(string)
(integer)))
(body
(pass_statement))))))
============================================
#61 Extends with a class name less than three characters causes a parse error
============================================
class_name UI extends Control
class_name Hello extends World
---
(source
(class_name_statement
(name)
(extends_statement (type (identifier))))
(class_name_statement
(name)
(extends_statement (type (identifier)))))
============================================
#21 Parse condition with comparison_operator and binary_operator wrong
============================================
1 == 2 or 3 == 4
---
(source
(expression_statement
(binary_operator
(binary_operator
(integer)
(integer))
(binary_operator
(integer)
(integer)))))
============================================
Variable right after lambda
============================================
func _on_pressed_button() -> void:
var root = func() -> Node:
var node: Node
var node_type: String = lib.editor.root.get_class()
match node_type:
"Node3D":
node = Node3D.new()
"Node2D":
node = Node2D.new()
return node
---
(source
(function_definition
(name)
(parameters)
(type (identifier))
(body
(variable_statement
(name)
(lambda
(parameters)
(type (identifier))
(body
(variable_statement
(name)
(type (identifier)))
(variable_statement
(name)
(type (identifier))
(attribute
(identifier)
(identifier)
(identifier)
(attribute_call
(identifier)
(arguments))))
(match_statement
(identifier)
(match_body
(pattern_section
(string)
(body
(expression_statement
(assignment
(identifier)
(attribute
(identifier)
(attribute_call
(identifier)
(arguments)))))))
(pattern_section
(string)
(body
(expression_statement
(assignment
(identifier)
(attribute
(identifier)
(attribute_call
(identifier)
(arguments)))))))))
(return_statement
(identifier))))))))
============================================
Highlight incorrect when just setting get
============================================
var selection:
get:
return interface.get_selection()
var children:
get:
return selected[0].get_children()
---
(source
(variable_statement
(name)
(setget
(get_body
(body
(return_statement
(attribute
(identifier)
(attribute_call
(identifier)
(arguments))))))))
(variable_statement
(name)
(setget
(get_body
(body
(return_statement
(attribute
(subscript
(identifier)
(subscript_arguments
(integer)))
(attribute_call
(identifier)
(arguments)))))))))
============================================
Export Var Stmt can be onready
============================================
export(NodePath) onready var path = get_node(path)
---
(source
(export_variable_statement
(arguments
(identifier))
(name)
(call
(identifier)
(arguments
(identifier)))))
============================================
Literals bugs with [get_node]
============================================
$"../inspectorHeader/inspectorHeaderHBoxContainer/CurrentBlock"
$"../../CommandsSettings"
$'../../Node'
---
(source
(expression_statement
(get_node))
(expression_statement
(get_node))
(expression_statement
(get_node)))
============================================
Fails to parse basic blocks
============================================
extends Node
func _ready():
var x := 2
for i in range(x):
prints(i)
while x > 0:
print(x)
if x > 0:
print("if test")
elif x < 0:
print("if test")
else:
print("if test")
---
(source
(extends_statement
(type (identifier)))
(function_definition
(name)
(parameters)
(body
(variable_statement
(name)
(inferred_type)
(integer))
(for_statement
(identifier)
(call
(identifier)
(arguments
(identifier)))
(body
(expression_statement
(call
(identifier)
(arguments
(identifier))))))
(while_statement
(binary_operator
(identifier)
(integer))
(body
(expression_statement
(call
(identifier)
(arguments
(identifier))))))
(if_statement
(binary_operator
(identifier)
(integer))
(body
(expression_statement
(call
(identifier)
(arguments
(string)))))
(elif_clause
(binary_operator
(identifier)
(integer))
(body
(expression_statement
(call
(identifier)
(arguments
(string))))))
(else_clause
(body
(expression_statement
(call
(identifier)
(arguments
(string))))))))))

@ -0,0 +1,36 @@
============================================
#73273 Lambda assign after if
============================================
if true: v = func(): test()
if true: v = func(): test()
---
(source
(if_statement
(true)
(body
(expression_statement
(assignment
(identifier)
(lambda
(parameters)
(body
(expression_statement
(call
(identifier)
(arguments)))))))))
(if_statement
(true)
(body
(expression_statement
(assignment
(identifier)
(lambda
(parameters)
(body
(expression_statement
(call
(identifier)
(arguments))))))))))

@ -0,0 +1,64 @@
============================================
Setget parse failure
============================================
var is_active = true:
set = set_is_active
@onready var _state = initial_state:
set = set_state
---
(source
(variable_statement
name: (name)
value: (true)
setget: (setget
set: (setter)))
(variable_statement
(annotations
(annotation
(identifier)))
name: (name)
value: (identifier)
setget: (setget
set: (setter))))
============================================
#35 Parsing: The valid syntax : set = ... : get = ... is not supported
============================================
var is_active := false: set = set_is_active
var test = -1: get = get_test
var x = true: get = g, set = s
var y = false: set = s, get = g
---
(source
(variable_statement
name: (name)
type: (inferred_type)
value: (false)
setget: (setget
set: (setter)))
(variable_statement
name: (name)
value: (unary_operator (integer))
setget: (setget
get: (getter)))
(variable_statement
name: (name)
value: (true)
setget: (setget
get: (getter)
set: (setter)))
(variable_statement
name: (name)
value: (false)
setget: (setget
set: (setter)
get: (getter)))
)

@ -0,0 +1,464 @@
=====================================
Lambdas Assignment
=====================================
var x = func hello():
pass
var x = func():
pass
var x = func hello(): pass
---
(source
(variable_statement
(name)
(lambda
(name)
(parameters)
(body
(pass_statement))))
(variable_statement
(name)
(lambda
(parameters)
(body
(pass_statement))))
(variable_statement
(name)
(lambda
(name)
(parameters)
(body
(pass_statement)))))
=====================================
Lambdas in Dictionary
=====================================
{key = func(): pass; pass, key = 1}
{key = 1, 'key': func():
pass
pass}
{key = 1, 'key': func():
pass
pass, 'key': 1}
---
(source
(expression_statement
(dictionary
(pair (identifier)
(lambda
(parameters)
(body
(pass_statement)
(pass_statement))))
(pair (identifier) (integer))))
(expression_statement
(dictionary
(pair (identifier) (integer))
(pair (string)
(lambda
(parameters)
(body
(pass_statement)
(pass_statement))))))
(expression_statement
(dictionary
(pair
(identifier)
(integer))
(pair
(string)
(lambda
(parameters)
(body
(pass_statement)
(pass_statement))))
(pair
(string)
(integer)))))
; =====================================
; Lambdas as Dictionary Keys
; =====================================
; {func():
; pass
; pass: 'value'}
; ---
; (source
; (expression_statement
; (dictionary
; (pair
; (lambda
; (parameters)
; (body
; (pass_statement)
; (pass_statement)))
; (string)))))
=====================================
Lambdas in Array
=====================================
[func x(): pass; pass]
[func x(): pass; pass,]
[func x():
pass
pass]
[func x():
pass
pass,]
[
func():
pass
pass,
func():
pass
pass,
]
func x():
if true:
var a = [func(): pass ; pass,]
var a = [func():
pass
pass,]
---
(source
(expression_statement
(array
(lambda
(name)
(parameters)
(body
(pass_statement)
(pass_statement)))))
(expression_statement
(array
(lambda
(name)
(parameters)
(body
(pass_statement)
(pass_statement)))))
(expression_statement
(array
(lambda
(name)
(parameters)
(body
(pass_statement)
(pass_statement)))))
(expression_statement
(array
(lambda
(name)
(parameters)
(body
(pass_statement)
(pass_statement)))))
(expression_statement
(array
(lambda
(parameters)
(body
(pass_statement)
(pass_statement)))
(lambda
(parameters)
(body
(pass_statement)
(pass_statement)))))
(function_definition
(name)
(parameters)
(body
(if_statement
(true)
(body
(variable_statement
(name)
(array
(lambda
(parameters)
(body
(pass_statement)
(pass_statement)))))
(variable_statement
(name)
(array
(lambda
(parameters)
(body
(pass_statement)
(pass_statement))))))))))
=====================================
Lambdas Nested
=====================================
var x = func(): (func hello(): pass) ; pass
---
(source
(variable_statement
(name)
(lambda
(parameters)
(body
(expression_statement
(parenthesized_expression
(lambda
(name)
(parameters)
(body
(pass_statement)))))
(pass_statement)))))
=====================================
Lambdas as Params
=====================================
func hello(p = func(p: int) -> int: pass; pass, x: int):
pass
func hello(p = func(p: int) -> int:
pass
pass, x: int):
pass
---
(source
(function_definition
(name)
(parameters
(default_parameter
(identifier)
(lambda
(parameters
(typed_parameter
(identifier)
(type (identifier))))
(type (identifier))
(body
(pass_statement)
(pass_statement))))
(typed_parameter
(identifier)
(type (identifier))))
(body
(pass_statement)))
(function_definition
(name)
(parameters
(default_parameter
(identifier)
(lambda
(parameters
(typed_parameter
(identifier)
(type (identifier))))
(type (identifier))
(body
(pass_statement)
(pass_statement))))
(typed_parameter
(identifier)
(type (identifier))))
(body
(pass_statement))))
=====================================
Lambdas as Args
=====================================
hello(func(): pass)
---
(source
(expression_statement
(call
(identifier)
(arguments
(lambda
(parameters)
(body
(pass_statement)))))))
=====================================
Lambdas in Statements
=====================================
func x(p: int = 1) -> int:
if p == 1:
var v = func(p: int) -> int:
var result = v + p
return result
return x(p)
else:
return (func(v: int) -> int:
var result = v + p
return result)(p)
---
(source
(function_definition
(name)
(parameters
(typed_default_parameter
(identifier)
(type (identifier))
(integer)))
(type (identifier))
(body
(if_statement
(binary_operator
(identifier)
(integer))
(body
(variable_statement
(name)
(lambda
(parameters
(typed_parameter
(identifier)
(type (identifier))))
(type (identifier))
(body
(variable_statement
(name)
(binary_operator
(identifier)
(identifier)))
(return_statement
(identifier)))))
(return_statement
(call
(identifier)
(arguments
(identifier)))))
(else_clause
(body
(return_statement
(call
(parenthesized_expression
(lambda
(parameters
(typed_parameter
(identifier)
(type (identifier))))
(type (identifier))
(body
(variable_statement
(name)
(binary_operator
(identifier)
(identifier)))
(return_statement
(identifier)))))
(arguments
(identifier))))))))))
=====================================
Lambdas Return Stmt
=====================================
var x = func():
pass
return func x(t: T) -> T:
pass
return t
---
(source
(variable_statement
(name)
(lambda
(parameters)
(body
(pass_statement)
(return_statement
(lambda
(name)
(parameters
(typed_parameter
(identifier)
(type (identifier))))
(type (identifier))
(body
(pass_statement)
(return_statement
(identifier)))))))))
=====================================
Lambdas Nested Ifs
=====================================
func x():
x(func():
if true:
y(func():
if true:
pass
else:
var x = 1 + 1 ,
"END y")
else:
pass, "END x")
---
(source
(function_definition
(name)
(parameters)
(body
(expression_statement
(call
(identifier)
(arguments
(lambda
(parameters)
(body
(if_statement
(true)
(body
(expression_statement
(call
(identifier)
(arguments
(lambda
(parameters)
(body
(if_statement
(true)
(body
(pass_statement))
(else_clause
(body
(variable_statement
(name)
(binary_operator
(integer)
(integer))))))))
(string)))))
(else_clause
(body
(pass_statement))))))
(string)))))))

@ -0,0 +1,148 @@
===
Syntax sugar line continuations
===
$get/node/\
my/node
%hello/ \
world
&"hello \
world"
---
(source
(expression_statement
(get_node
(line_continuation)))
(expression_statement
(get_node
(line_continuation)))
(expression_statement
(string_name
(escape_sequence))))
===
Invalid syntax sugar line continuations
===
# This is invalid GDScript but I'm making the parser accept it for the sake of simplicity with line continuations
$get\
/node
---
(source
(comment)
(expression_statement
(get_node
(line_continuation))))
===
Line continuation in binary expressions
===
func _handles(resource):
return resource is NoiseTexture2D \
or resource is GradientTexture1D
---
(source
(function_definition
name: (name)
parameters: (parameters
(identifier))
body: (body
(return_statement
(binary_operator
left: (binary_operator
left: (identifier)
right: (identifier))
(line_continuation)
right: (binary_operator
left: (identifier)
right: (identifier)))))))
===
Line continuation in function calls
===
func _process(delta):
move_and_slide(velocity * \
delta)
---
(source
(function_definition
name: (name)
parameters: (parameters
(identifier))
body: (body
(expression_statement
(call
(identifier)
arguments: (arguments
(binary_operator
left: (identifier)
(line_continuation)
right: (identifier))))))))
===
Line continuation in array literals
===
func test():
var array = [
1, \
2, \
3
]
---
(source
(function_definition
name: (name)
parameters: (parameters)
body: (body
(variable_statement
name: (name)
value: (array
(integer)
(line_continuation)
(integer)
(line_continuation)
(integer))))))
===
Line continuation in dictionary
===
func test():
var dict = {
"key": \
"value",
"key2": \
"value2"
}
---
(source
(function_definition
name: (name)
parameters: (parameters)
body: (body
(variable_statement
name: (name)
value: (dictionary
(pair
left: (string)
value: (string))
(pair
left: (string)
value: (string)))))))

@ -0,0 +1,269 @@
=====================================
Single
=====================================
match x:
1: pass
_: pass
TYPE_ARRAY:
pass
var new_var:
pass
---
(source
(match_statement (identifier) (match_body
(pattern_section (integer) (body (pass_statement)))
(pattern_section (identifier) (body (pass_statement)))
(pattern_section (identifier) (body (pass_statement)))
(pattern_section (pattern_binding (identifier)) (body (pass_statement)))
)))
=====================================
Multiple
=====================================
match x:
_:
pass
1, 2, 3: pass
---
(source
(match_statement (identifier) (match_body
(pattern_section (identifier) (body (pass_statement)))
(pattern_section (integer) (integer) (integer)
(body (pass_statement)))
)))
=====================================
Arrays
=====================================
match x:
[]: pass
[1, 3, "test", null]:
pass
[var start, _]: pass
[42, ..]:
pass
---
(source
(match_statement (identifier) (match_body
(pattern_section (array) (body (pass_statement)))
(pattern_section
(array (integer) (integer) (string) (null))
(body (pass_statement)))
(pattern_section
(array (pattern_binding (identifier)) (identifier))
(body (pass_statement)))
(pattern_section
(array (integer) (pattern_open_ending))
(body (pass_statement)))
)))
=====================================
Dicts
=====================================
match x:
{}: pass
{"name": "Dennis"}: pass
{"name": "Dennis", "age": var age}: pass
{"key": "godotisawesome", ..}: pass
---
(source
(match_statement (identifier) (match_body
(pattern_section (dictionary) (body (pass_statement)))
(pattern_section
(dictionary (pair (string) (string)))
(body (pass_statement)))
(pattern_section
(dictionary
(pair (string) (string))
(pair (string) (pattern_binding (identifier))))
(body (pass_statement)))
(pattern_section
(dictionary
(pair (string) (string))
(pattern_open_ending))
(body (pass_statement)))
)))
=====================================
Expressions
=====================================
match x:
Hello.World: pass
Hello.World(): pass
function(): pass
Color().a: pass
[var hello, _, ..]: pass
---
(source
(match_statement (identifier) (match_body
(pattern_section
(attribute (identifier) (identifier))
(body (pass_statement)))
(pattern_section
(attribute (identifier) (attribute_call (identifier) (arguments)))
(body (pass_statement)))
(pattern_section
(call (identifier) (arguments))
(body (pass_statement)))
(pattern_section
(attribute
(call (identifier) (arguments))
(identifier))
(body (pass_statement)))
(pattern_section
(array
(pattern_binding (identifier))
(identifier)
(pattern_open_ending))
(body (pass_statement))))))
============================================
Match: conditional expression, annotation, and call
============================================
match get_value():
result if run_check() else fallback:
@warning_ignore("test")
handle_value()
---
(source
(match_statement
value: (call
(identifier)
arguments: (arguments))
body: (match_body
(pattern_section
(conditional_expression
left: (identifier)
condition: (call
(identifier)
arguments: (arguments))
right: (identifier))
body: (body
(annotation
(identifier)
arguments: (arguments
(string)))
(expression_statement
(call
(identifier)
arguments: (arguments))))))))
============================================
Match: Simple conditional expression
============================================
match 0:
0 if true else 2:
pass
---
(source
(match_statement
value: (integer)
body: (match_body
(pattern_section
(conditional_expression
left: (integer)
condition: (true)
right: (integer))
body: (body
(pass_statement))))))
============================================
Match: Nested conditional expressions with identifiers
============================================
match value:
x if condition else y:
print("matched")
a if b else c if d else e:
print("complex ternary")
---
(source
(match_statement
value: (identifier)
body: (match_body
(pattern_section
(conditional_expression
left: (identifier)
condition: (identifier)
right: (identifier))
body: (body
(expression_statement
(call
(identifier)
arguments: (arguments
(string))))))
(pattern_section
(conditional_expression
left: (identifier)
condition: (identifier)
right: (conditional_expression
left: (identifier)
condition: (identifier)
right: (identifier)))
body: (body
(expression_statement
(call
(identifier)
arguments: (arguments
(string)))))))))
============================================
Match: Annotations within match statements
============================================
match 1:
_:
print(0)
@warning_ignore("unreachable_pattern")
1:
print(1)
---
(source
(match_statement
value: (integer)
body: (match_body
(pattern_section
(identifier)
body: (body
(expression_statement
(call
(identifier)
arguments: (arguments
(integer))))))
(annotation
(identifier)
arguments: (arguments
(string)))
(pattern_section
(integer)
body: (body
(expression_statement
(call
(identifier)
arguments: (arguments
(integer)))))))))

@ -0,0 +1,133 @@
=====================================
Region: Empty region
=====================================
#region empty
#endregion
---
(source
(region_start
(region_label))
(region_end))
=====================================
Region: Basic anonymous region with code
=====================================
#region
func _ready() -> void:
pass
#endregion
---
(source
(region_start)
(function_definition
(name)
(parameters)
(type (identifier))
(body (pass_statement)))
(region_end))
=====================================
Region: Named region with more code
=====================================
#region process
func _process(delta: float) -> void:
position += velocity * delta
#endregion
---
(source
(region_start
(region_label))
(function_definition
(name)
(parameters
(typed_parameter
(identifier)
(type (identifier))))
(type (identifier))
(body
(expression_statement
(augmented_assignment
(identifier)
(binary_operator
(identifier)
(identifier))))))
(region_end))
=====================================
Region: Region with leading indentation
=====================================
func _physics_process(delta: float) -> void:
#region movement
pass
#endregion
---
(source
(function_definition
(name)
(parameters
(typed_parameter
(identifier)
(type (identifier))))
(type (identifier))
(body
(region_start
(region_label))
(pass_statement)
(region_end))))
=====================================
Region: Two sibling regions
=====================================
#region variables
var health: int = 100
var speed: float = 5.0
#endregion
#region functions
func take_damage(amount) -> void:
health -= amount
#endregion
---
(source
(region_start
(region_label))
(variable_statement
(name)
(type
(identifier))
(integer))
(variable_statement
(name)
(type
(identifier))
(float))
(region_end)
(region_start
(region_label))
(function_definition
(name)
(parameters
(identifier))
(type
(identifier))
(body
(expression_statement
(augmented_assignment
(identifier)
(identifier)))))
(region_end))

@ -0,0 +1,50 @@
=====================================
Sample - GDScript 1
=====================================
extends Node2D
class_name CustomNode, "icon_path"
class Data extends Resource:
var source: DataSource
func process_source() -> ProcessedSource:
if source.is_processable():
var processed = ProcessedSource.new(source)
return processed
return null
func _ready():
for i in range(100):
var child = Node.new()
child.name = str("Name-", i)
add_child(child)
---
(source
(extends_statement (type (identifier)))
(class_name_statement (name) (string))
(class_definition (name) (extends_statement (type (identifier)))
(class_body
(variable_statement (name) (type (identifier)))
(function_definition (name) (parameters) (type (identifier))
(body
(if_statement (attribute (identifier) (attribute_call (identifier) (arguments)))
(body
(variable_statement (name)
(attribute (identifier) (attribute_call (identifier) (arguments (identifier)))))
(return_statement (identifier))))
(return_statement (null))))))
(function_definition (name) (parameters)
(body
(for_statement (identifier) (call (identifier) (arguments (integer)))
(body
(variable_statement (name)
(attribute (identifier) (attribute_call (identifier) (arguments))))
(expression_statement
(assignment
(attribute (identifier) (identifier))
(call (identifier) (arguments (string) (identifier)))))
(expression_statement
(call (identifier) (arguments (identifier)))))))))

File diff suppressed because it is too large Load Diff

@ -0,0 +1,179 @@
============================================
Typed Arrays (#18)
============================================
var a1: Array[IClass.IIClass]
var a3: Array[int]
---
(source
(variable_statement
(name)
(type
(subscript
(identifier)
(subscript_arguments
(attribute
(identifier)
(identifier))))))
(variable_statement
(name)
(type
(subscript
(identifier)
(subscript_arguments
(identifier))))))
============================================
Static Variable Statements
============================================
static var name
static var name := "Me"
static var name: int
# There may be valid annotations for static vars.
@some_annotation static var name
---
(source
(variable_statement (static_keyword) (name))
(variable_statement (static_keyword) (name) (inferred_type) (string))
(variable_statement (static_keyword) (name) (type (identifier)))
(comment)
(variable_statement
(annotations (annotation (identifier)))
(static_keyword)
(name)))
============================================
Static Typed For Loop
============================================
for i: int in [1, 2]:
pass
---
(source
(for_statement
(identifier)
(type (identifier))
(array
(integer)
(integer))
(body
(pass_statement))))
============================================
SetGet Getter with Empty Parenthesis
============================================
var x:
get(): pass
---
(source
(variable_statement
(name)
(setget
(get_body
(body
(pass_statement))))))
============================================
Pattern Guards for Match Statement
============================================
var a = 0
match a:
0 when false: print("does not run")
0 when true: print("but this does")
var when = 1
match when:
when when when:
when
---
(source
(variable_statement (name) (integer))
(match_statement
(identifier)
(match_body
(pattern_section
(integer)
(pattern_guard
(false))
(body
(expression_statement
(call
(identifier)
(arguments
(string))))))
(pattern_section
(integer)
(pattern_guard
(true))
(body
(expression_statement
(call
(identifier)
(arguments
(string))))))))
(variable_statement (name) (integer))
(match_statement
(identifier)
(match_body
(pattern_section
(identifier)
(pattern_guard
(identifier))
(body
(expression_statement (identifier)))))))
============================================
Is Not Test
============================================
x is T
x is not T
x is (not T)
---
(source
(expression_statement (binary_operator (identifier) (identifier)))
(expression_statement (binary_operator (identifier) (identifier)))
(expression_statement (binary_operator (identifier) (parenthesized_expression (unary_operator (identifier))))))
============================================
In And Not In Test
============================================
x in y
x not in y
not (x in y)
---
(source
(expression_statement
(binary_operator
(identifier)
(identifier)))
(expression_statement
(binary_operator
(identifier)
(identifier)))
(expression_statement
(unary_operator
(parenthesized_expression
(binary_operator
(identifier)
(identifier))))))

@ -0,0 +1,671 @@
===============================================================
Double-quoted regular string
===============================================================
"I used to be an adventurer like you."
---
(source
(expression_statement
(string)))
===============================================================
Double-quoted regular string containing escape sequence
===============================================================
"Then i \took an arrow in \the knee."
---
(source
(expression_statement
(string
(escape_sequence)
(escape_sequence))))
===============================================================
Double-quoted raw string
===============================================================
r"Wake up Mr. Freeman."
---
(source
(expression_statement
(string)))
===============================================================
Double-quoted raw string containing escape sequence
===============================================================
r"Wake up and smell \the ashes."
---
(source
(expression_statement
(string)))
===============================================================
Single-quoted regular string
===============================================================
'Would you kindly?'
---
(source
(expression_statement
(string)))
===============================================================
Single-quoted regular string containing escape sequence
===============================================================
'Wind\'s howling.'
---
(source
(expression_statement
(string (escape_sequence))))
===============================================================
Single-quoted raw string
===============================================================
r'Patrolling the Mojave almost makes you wish for a nuclear winter.'
---
(source
(expression_statement
(string)))
===============================================================
Single-quoted raw string containing escape sequence
===============================================================
r'Praise \the sun.'
---
(source
(expression_statement
(string)))
===============================================================
Triple double-quoted regular string
===============================================================
"""Snake?
Snake?!
SNAAAAKE!"""
---
(source
(expression_statement
(string)))
===============================================================
Triple double-quoted regular string containing escape sequence
===============================================================
"""It's \too dangerous \to go alone!
Take this."""
---
(source
(expression_statement
(string
(escape_sequence)
(escape_sequence))))
===============================================================
Triple double-quoted raw string
===============================================================
r"""Requiescat
in
pace."""
---
(source
(expression_statement
(string)))
===============================================================
Triple double-quoted raw string containing escape sequence
===============================================================
r"""Rip and \tear,
until it's done"""
---
(source
(expression_statement
(string)))
===============================================================
Triple single-quoted regular string
===============================================================
'''You must construct
additional pylons!'''
---
(source
(expression_statement
(string)))
===============================================================
Triple single-quoted regular string containing escape sequence
===============================================================
'''We\'re more ghost
\than people.'''
---
(source
(expression_statement
(string
(escape_sequence)
(escape_sequence))))
===============================================================
Triple single-quoted raw string
===============================================================
r'''Heroes
never
die!'''
---
(source
(expression_statement
(string)))
===============================================================
Triple single-quoted raw string containing escape sequence
===============================================================
r'''For
\the
a\\iance!'''
---
(source
(expression_statement
(string)))
===============================================================
Double-quoted string_name
===============================================================
&"Hadouken!"
---
(source
(expression_statement
(string_name)))
===============================================================
Double-quoted string_name containing escape sequence
===============================================================
&"No cost \too great."
---
(source
(expression_statement
(string_name (escape_sequence))))
===============================================================
Single-quoted string_name
===============================================================
&'Why do we fight?'
---
(source
(expression_statement
(string_name)))
===============================================================
Single-quoted string_name containing escape sequence
===============================================================
&'Cut off \their limbs!'
---
(source
(expression_statement
(string_name (escape_sequence))))
===============================================================
Triple double-quoted string_name
===============================================================
&"""It's a
mad world, kid."""
---
(source
(expression_statement
(string_name)))
===============================================================
Triple double-quoted string_name containing escape sequence
===============================================================
&"""And it's only
ge\t\ting madder."""
---
(source
(expression_statement
(string_name
(escape_sequence)
(escape_sequence))))
===============================================================
Triple single-quoted string_name
===============================================================
&'''I am
fragile'''
---
(source
(expression_statement
(string_name)))
===============================================================
Triple single-quoted string_name containing escape sequence
===============================================================
&'''but not
\that fragile.'''
---
(source
(expression_statement
(string_name (escape_sequence))))
===============================================================
Double-quoted node_path
===============================================================
^"./../Sandwich/makes/me:strong"
---
(source
(expression_statement
(node_path)))
===============================================================
Double-quoted node_path containing escape sequence
===============================================================
^"../../Ain't/no/rest/for/\the:wicked!"
---
(source
(expression_statement
(node_path (escape_sequence))))
===============================================================
Single-quoted node_path
===============================================================
^'Did/i/ever/tell/you/definition:of:insanity?'
---
(source
(expression_statement
(node_path)))
===============================================================
Single-quoted node_path containing escape sequence
===============================================================
^'You\'ve/met/with/a/terrible/fate:haven\'t:you?'
---
(source
(expression_statement
(node_path
(escape_sequence)
(escape_sequence))))
===============================================================
Triple double-quoted node_path
===============================================================
^"""We/stand/upon/the
/precipice:of:change"""
---
(source
(expression_statement
(node_path)))
===============================================================
Triple double-quoted node_path containing escape sequence
===============================================================
^"""The/pas\t/is/a/puzzle
/like/a/broken:mirror"""
---
(source
(expression_statement
(node_path (escape_sequence))))
===============================================================
Triple single-quoted node_path
===============================================================
^'''As/you/piece/it/together
/you:cut/yourself'''
---
(source
(expression_statement
(node_path)))
==============================================================
Triple single-quoted node_path containing escape sequence
==============================================================
^'''Do/a/barrel
:ro\\'''
---
(source
(expression_statement
(node_path (escape_sequence))))
==============================================================
Unquoted get_node
==============================================================
$_We/all/start/with/innocence
%But/the/world/leads/us/to/guilt_
---
(source
(expression_statement
(get_node))
(expression_statement
(get_node)))
==============================================================
Double-quoted get_node
==============================================================
$"0Your/health/is:low"
%"0Do/you/any/potions/or:food?"
---
(source
(expression_statement
(get_node))
(expression_statement
(get_node)))
==============================================================
Double-quoted get_node containing escape sequence
==============================================================
$"\the/power:is:yours"
%"\return/to\the/mission:"
---
(source
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node
(escape_sequence)
(escape_sequence))))
==============================================================
Single-quoted get_node
==============================================================
$'../They/call/me:zero'
%'Because/i/am:nothing'
---
(source
(expression_statement
(get_node))
(expression_statement
(get_node)))
==============================================================
Single-quoted get_node containing escape sequence
==============================================================
$'People/don\'t/forget'
%'Nothing/gets:\forgiven'
---
(source
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node (escape_sequence))))
==============================================================
Triple double-quoted get_node
==============================================================
$"""../A hunter
/is:hunter"""
%"""./Even/in
/a:dream"""
---
(source
(expression_statement
(get_node))
(expression_statement
(get_node)))
==============================================================
Triple double-quoted get_node containing escape sequence
==============================================================
$"""This/was
/a:\triumph"""
%"""I'm/making/a/no\te/here
/huge:success"""
---
(source
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node (escape_sequence))))
==============================================================
Triple single-quoted get_node
==============================================================
$'''Honor/
died'''
%'''on/the
/beach'''
---
(source
(expression_statement
(get_node))
(expression_statement
(get_node)))
==============================================================
Triple single-quoted get_node containing escape sequence
==============================================================
$'''Stop/right:
\there'''
%'''Crimina\\:
scum'''
---
(source
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node (escape_sequence))))
==============================================================
Extras and errors
==============================================================
# Somehow, these are perfectly valid.
# Maybe a bug?
# Probably because they look like literals but they aren't.
$ _Fus/ro / dah
% You/are/finally/ \
awake
# These are not valid.
# It's because, there must be at least and at most one "/" symbol between the identifiers
$Let me/guess
%Someone//stole/your/sweetrool
# Btw, $ nodepath supports "/" symbol at the begining
$/i/m/not/even/angry
# But % uniquenode does not.
# (This might be a bug)
%/open/your/eyes
# These are valid too.
$ "Why/did/i \
/move:here?"
% "I/guess/it:was:\the \
:weather"
$ 'Ah/s***'
% '../here/we/go: \
again'
$ """Here/come/the\test/results:"""
% \
"""You \
are/a/horrible:person"""
$ '''Punc/a\tree'''
% '''\to/gather:wood'''
# But these are not valid.
# They should be a token.
var v = & ""
var v = & ''
var v = & """"""
var v = & ''''''
var v = r ""
var v = r ''
var v = r """"""
var v = r ''''''
var v = ^ ""
var v = ^ ''
var v = ^ """"""
var v = ^ ''''''
---
(source
(comment)
(comment)
(comment)
(expression_statement
(get_node))
(expression_statement
(get_node
(line_continuation)))
(comment)
(comment)
(expression_statement
(get_node (ERROR)))
(expression_statement
(get_node (ERROR)))
(comment)
(expression_statement
(get_node))
(comment)
(comment)
(expression_statement
(get_node (ERROR)))
(comment)
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node
(escape_sequence)
(escape_sequence)))
(expression_statement
(get_node))
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node (escape_sequence)))
(expression_statement
(get_node (escape_sequence)))
(comment)
(comment)
(variable_statement (name) (ERROR) (string))
(variable_statement (name) (ERROR) (string))
(variable_statement (name) (ERROR) (string))
(variable_statement (name) (ERROR) (string))
(variable_statement (name) (ERROR (identifier)) (string))
(variable_statement (name) (ERROR (identifier)) (string))
(variable_statement (name) (ERROR (identifier)) (string))
(variable_statement (name) (ERROR (identifier)) (string))
(variable_statement (name) (ERROR) (string))
(variable_statement (name) (ERROR) (string))
(variable_statement (name) (ERROR) (string))
(variable_statement (name) (ERROR) (string)))

@ -0,0 +1,25 @@
============================================
Subscripts
============================================
T[1, 2]
T.T[1, 2, 3]
---
(source
(expression_statement
(subscript
(identifier)
(subscript_arguments
(integer)
(integer))))
(expression_statement
(attribute
(identifier)
(attribute_subscript
(identifier)
(subscript_arguments
(integer)
(integer)
(integer))))))

@ -0,0 +1,35 @@
{
"grammars": [
{
"name": "gdscript",
"camelcase": "GDScript",
"scope": "source.gdscript",
"file-types": [
".gd"
],
"injection-regex": "^gdscript$"
}
],
"metadata": {
"version": "6.0.0",
"license": "MIT",
"description": "Grammar for Godot's built-in scripting language.",
"authors": [
{
"name": "Preston Knopp",
"email": "prestonknopp@gmail.com"
}
],
"links": {
"repository": "https://github.com/PrestonKnopp/tree-sitter-gdscript"
}
},
"bindings": {
"c": true,
"go": true,
"node": true,
"python": true,
"rust": true,
"swift": true
}
}

@ -0,0 +1 @@
tree-sitter-godot-resource/src/

@ -0,0 +1,46 @@
root = true
[*]
charset = utf-8
[*.{json,toml,yml,gyp}]
indent_style = space
indent_size = 2
[*.js]
indent_style = space
indent_size = 2
[*.scm]
indent_style = space
indent_size = 2
[*.{c,cc,h}]
indent_style = space
indent_size = 4
[*.rs]
indent_style = space
indent_size = 4
[*.{py,pyi}]
indent_style = space
indent_size = 4
[*.swift]
indent_style = space
indent_size = 4
[*.go]
indent_style = tab
indent_size = 8
[Makefile]
indent_style = tab
indent_size = 8
[parser.c]
indent_size = 2
[{alloc,array,parser}.h]
indent_size = 2

@ -0,0 +1,37 @@
* text=auto eol=lf
# Generated source files
src/*.json linguist-generated
src/parser.c linguist-generated
src/tree_sitter/* linguist-generated
# C bindings
bindings/c/* linguist-generated
CMakeLists.txt linguist-generated
Makefile linguist-generated
# Rust bindings
bindings/rust/* linguist-generated
Cargo.toml linguist-generated
Cargo.lock linguist-generated
# Node.js bindings
bindings/node/* linguist-generated
binding.gyp linguist-generated
package.json linguist-generated
package-lock.json linguist-generated
# Python bindings
bindings/python/** linguist-generated
setup.py linguist-generated
pyproject.toml linguist-generated
# Go bindings
bindings/go/* linguist-generated
go.mod linguist-generated
go.sum linguist-generated
# Swift bindings
bindings/swift/** linguist-generated
Package.swift linguist-generated
Package.resolved linguist-generated

@ -0,0 +1,73 @@
# This workflow will
# - test tree sitter grammar
# - upload build native binaries as an artifact for each major platform
# - download artifacts for each major platform and bundle them to be published
# to npm
# when a new version tag is pushed to the master branch.
name: Test Build Publish
on:
push:
tags: [v*]
jobs:
build_native_binaries:
strategy:
matrix:
# Use macos-14 for arm, however the artifact upload name conflicts with
# macos-latest. There's probably a way to crossbuild for arm with
# prebuildify.
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
# Why is npm only occassionally installing peer dependencies?
# I cannot get consistent peer dependency installs with the same command runs.
# Should I always explicitly run npm i tree-sitter? The general consensus is yes.
# But then, why does it install peer deps sometimes?
- run: |
node --version
npm --version
- run: npm ci --include=peer --include=optional --include=dev
- run: npm i tree-sitter
- run: npm run versions
- run: npm test
- run: npm run prebuild
# upload-artifact@v4 requires each artifact name to be unique.
- uses: actions/upload-artifact@v4
with:
name: prebuilds-${{ matrix.os }}
path: prebuilds
retention-days: 1
publish_npm:
needs: build_native_binaries
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
# https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-nodejs-packages#publishing-packages-to-the-npm-registry
# https://github.com/actions/setup-node/issues/342
# This is required to publish to npm.
registry-url: 'https://registry.npmjs.org'
- run: |
node --version
npm --version
- run: npm ci
# Download all artifacts and merge into prebuilds dir.
- uses: actions/download-artifact@v4
with:
path: prebuilds
pattern: prebuilds-*
merge-multiple: true
- run: ls -R prebuilds
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

@ -0,0 +1,22 @@
name: Test
on:
push:
paths:
- 'grammar.js'
- 'corpus/**'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- run: |
node --version
npm --version
- run: npm ci --include=dev --include=optional --include=peer
- run: npm test

@ -0,0 +1,41 @@
# Rust artifacts
target/
# Node artifacts
build/
prebuilds/
node_modules/
# Swift artifacts
.build/
# Go artifacts
_obj/
# Python artifacts
.venv/
dist/
*.egg-info
*.whl
# C artifacts
*.a
*.so
*.so.*
*.dylib
*.dll
*.pc
parser.exp
parser.lib
parser.obj
scanner.obj
# Grammar volatiles
*.wasm
*.obj
*.o
# Archives
*.tar.gz
*.tgz
*.zip

@ -0,0 +1,8 @@
corpus
examples
build
script
parser.exp
parser.lib
parser.obj
scanner.obj

@ -0,0 +1,58 @@
cmake_minimum_required(VERSION 3.13)
project(tree-sitter-godot-resource
VERSION "0.7.0"
DESCRIPTION "Grammar for the Godot game engine's resource format."
HOMEPAGE_URL "https://github.com/prestonknopp/tree-sitter-godot-resource"
LANGUAGES C)
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
option(TREE_SITTER_REUSE_ALLOCATOR "Reuse the library allocator" OFF)
set(TREE_SITTER_ABI_VERSION 14 CACHE STRING "Tree-sitter ABI version")
if(NOT ${TREE_SITTER_ABI_VERSION} MATCHES "^[0-9]+$")
unset(TREE_SITTER_ABI_VERSION CACHE)
message(FATAL_ERROR "TREE_SITTER_ABI_VERSION must be an integer")
endif()
find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI")
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
--abi=${TREE_SITTER_ABI_VERSION}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Generating parser.c")
add_library(tree-sitter-godot-resource src/parser.c)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c)
target_sources(tree-sitter-godot-resource PRIVATE src/scanner.c)
endif()
target_include_directories(tree-sitter-godot-resource PRIVATE src)
target_compile_definitions(tree-sitter-godot-resource PRIVATE
$<$<BOOL:${TREE_SITTER_REUSE_ALLOCATOR}>:TREE_SITTER_REUSE_ALLOCATOR>
$<$<CONFIG:Debug>:TREE_SITTER_DEBUG>)
set_target_properties(tree-sitter-godot-resource
PROPERTIES
C_STANDARD 11
POSITION_INDEPENDENT_CODE ON
SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}"
DEFINE_SYMBOL "")
configure_file(bindings/c/tree-sitter-godot-resource.pc.in
"${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-godot-resource.pc" @ONLY)
include(GNUInstallDirs)
install(FILES bindings/c/tree-sitter-godot-resource.h
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/tree_sitter")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-godot-resource.pc"
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig")
install(TARGETS tree-sitter-godot-resource
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}")
add_custom_target(ts-test "${TREE_SITTER_CLI}" test
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "tree-sitter test")

@ -0,0 +1,27 @@
[package]
name = "tree-sitter-godot-resource"
description = "Grammar for the Godot game engine's resource format."
version = "0.7.0"
authors = ["Preston Knopp"]
license = "MIT"
readme = "README.md"
keywords = ["incremental", "parsing", "tree-sitter", "godot-resource"]
categories = ["parsing", "text-editors"]
repository = "https://github.com/prestonknopp/tree-sitter-godot-resource"
edition = "2021"
autoexamples = false
build = "bindings/rust/build.rs"
include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json"]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter-language = "0.1"
[build-dependencies]
cc = "1.1.22"
[dev-dependencies]
tree-sitter = "0.24.6"

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Preston Knopp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,94 @@
ifeq ($(OS),Windows_NT)
$(error Windows is not supported)
endif
LANGUAGE_NAME := tree-sitter-godot-resource
HOMEPAGE_URL := https://github.com/prestonknopp/tree-sitter-godot-resource
VERSION := 0.7.0
# repository
SRC_DIR := src
TS ?= tree-sitter
# install directory layout
PREFIX ?= /usr/local
INCLUDEDIR ?= $(PREFIX)/include
LIBDIR ?= $(PREFIX)/lib
PCLIBDIR ?= $(LIBDIR)/pkgconfig
# source/object files
PARSER := $(SRC_DIR)/parser.c
EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c))
OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS))
# flags
ARFLAGS ?= rcs
override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC
# ABI versioning
SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER))
SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION)))
# OS-specific bits
ifeq ($(shell uname),Darwin)
SOEXT = dylib
SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT)
SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT)
LINKSHARED = -dynamiclib -Wl,-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SOEXTVER),-rpath,@executable_path/../Frameworks
else
SOEXT = so
SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR)
SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR)
LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER)
endif
ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),)
PCLIBDIR := $(PREFIX)/libdata/pkgconfig
endif
all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc
lib$(LANGUAGE_NAME).a: $(OBJS)
$(AR) $(ARFLAGS) $@ $^
lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS)
$(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@
ifneq ($(STRIP),)
$(STRIP) $@
endif
$(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in
sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \
-e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \
-e 's|@CMAKE_INSTALL_INCLUDEDIR@|$(INCLUDEDIR:$(PREFIX)/%=%)|' \
-e 's|@PROJECT_DESCRIPTION@|$(DESCRIPTION)|' \
-e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \
-e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@
$(PARSER): $(SRC_DIR)/grammar.json
$(TS) generate $^
install: all
install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
install -m644 bindings/c/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h
install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a
install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER)
ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR)
ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT)
uninstall:
$(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \
'$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \
'$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
clean:
$(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT)
test:
$(TS) test
.PHONY: all install uninstall clean test

@ -0,0 +1,37 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "TreeSitterGodotResource",
products: [
.library(name: "TreeSitterGodotResource", targets: ["TreeSitterGodotResource"]),
],
dependencies: [
.package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.8.0"),
],
targets: [
.target(
name: "TreeSitterGodotResource",
dependencies: [],
path: ".",
sources: [
"src/parser.c",
"src/scanner.c",
],
resources: [
.copy("queries")
],
publicHeadersPath: "bindings/swift",
cSettings: [.headerSearchPath("src")]
),
.testTarget(
name: "TreeSitterGodotResourceTests",
dependencies: [
"SwiftTreeSitter",
"TreeSitterGodotResource",
],
path: "bindings/swift/TreeSitterGodotResourceTests"
)
],
cLanguageStandard: .c11
)

@ -0,0 +1,5 @@
# tree-sitter-godot-resource
Godot resource grammar for [tree-sitter](https://github.com/tree-sitter/tree-sitter).
Parses tscn and tres files.

@ -0,0 +1,31 @@
{
"targets": [
{
"target_name": "tree_sitter_godot_resource_binding",
"dependencies": [
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
],
"include_dirs": [
"src",
],
"sources": [
"bindings/node/binding.cc",
"src/parser.c",
# NOTE: if your language has an external scanner, add it here.
"src/scanner.c",
],
"conditions": [
["OS!='win'", {
"cflags_c": [
"-std=c11",
],
}, { # OS == "win"
"cflags_c": [
"/std:c11",
"/utf-8",
],
}],
],
}
]
}

@ -0,0 +1,16 @@
#ifndef TREE_SITTER_GODOT_RESOURCE_H_
#define TREE_SITTER_GODOT_RESOURCE_H_
typedef struct TSLanguage TSLanguage;
#ifdef __cplusplus
extern "C" {
#endif
const TSLanguage *tree_sitter_godot_resource(void);
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_GODOT_RESOURCE_H_

@ -0,0 +1,10 @@
prefix=@CMAKE_INSTALL_PREFIX@
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: tree-sitter-godot-resource
Description: @PROJECT_DESCRIPTION@
URL: @PROJECT_HOMEPAGE_URL@
Version: @PROJECT_VERSION@
Libs: -L${libdir} -ltree-sitter-godot-resource
Cflags: -I${includedir}

@ -0,0 +1,14 @@
package tree_sitter_godot_resource
// #cgo CFLAGS: -std=c11 -fPIC
// #include "../../src/parser.c"
// // NOTE: if your language has an external scanner, add it here.
// #include "../../src/scanner.c"
import "C"
import "unsafe"
// Get the tree-sitter Language for this grammar.
func Language() unsafe.Pointer {
return unsafe.Pointer(C.tree_sitter_godot_resource())
}

@ -0,0 +1,15 @@
package tree_sitter_godot_resource_test
import (
"testing"
tree_sitter "github.com/tree-sitter/go-tree-sitter"
tree_sitter_godot_resource "github.com/prestonknopp/tree-sitter-godot-resource/bindings/go"
)
func TestCanLoadGrammar(t *testing.T) {
language := tree_sitter.NewLanguage(tree_sitter_godot_resource.Language())
if language == nil {
t.Errorf("Error loading GodotResource grammar")
}
}

@ -0,0 +1,9 @@
# This module, i.e. the nim bindings, can be placed anywhere in your nim project
# or kept in the bindings directory.
#
# However the following assumes that when compiling your project or main module that
# "tree-sitter-godot-resource/" is accessible from the current working directory.
{.passC: "-Itree-sitter-godot-resource/src".}
{.compile: "tree-sitter-godot-resource/src/parser.c".}
{.compile: "tree-sitter-godot-resource/src/scanner.c".}
proc tree_sitter_godot_resource*(): pointer {.importc.}

@ -0,0 +1,20 @@
#include <napi.h>
typedef struct TSLanguage TSLanguage;
extern "C" TSLanguage *tree_sitter_godot_resource();
// "tree-sitter", "language" hashed with BLAKE2
const napi_type_tag LANGUAGE_TYPE_TAG = {
0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
};
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports["name"] = Napi::String::New(env, "godot_resource");
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_godot_resource());
language.TypeTag(&LANGUAGE_TYPE_TAG);
exports["language"] = language;
return exports;
}
NODE_API_MODULE(tree_sitter_godot_resource_binding, Init)

@ -0,0 +1,9 @@
const assert = require("node:assert");
const { test } = require("node:test");
const Parser = require("tree-sitter");
test("can load grammar", () => {
const parser = new Parser();
assert.doesNotThrow(() => parser.setLanguage(require(".")));
});

@ -0,0 +1,28 @@
type BaseNode = {
type: string;
named: boolean;
};
type ChildNode = {
multiple: boolean;
required: boolean;
types: BaseNode[];
};
type NodeInfo =
| (BaseNode & {
subtypes: BaseNode[];
})
| (BaseNode & {
fields: { [name: string]: ChildNode };
children: ChildNode[];
});
type Language = {
name: string;
language: unknown;
nodeTypeInfo: NodeInfo[];
};
declare const language: Language;
export = language;

Some files were not shown because too many files have changed in this diff Show More