From a34705d64218d8f33b589d2102107557d58552f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20L=2E=20Charlier?= Date: Sat, 8 Aug 2026 23:38:18 +0200 Subject: [PATCH 1/6] chore: initialize repository metadata --- .gitattributes | 18 ++++ .github/conventional_commits_labels.json | 14 +++ .github/dependabot.yml | 22 +++++ .github/release.yml | 17 ++++ .gitignore | 42 +++++++++ AGENTS.md | 66 ++++++++++++++ CONTRIBUTING.md | 61 +++++++++++++ README.md | 109 +++++++++++++++++++++++ SECURITY.md | 14 +++ 9 files changed, 363 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/conventional_commits_labels.json create mode 100644 .github/dependabot.yml create mode 100644 .github/release.yml create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 SECURITY.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8f4a379 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Normalize text files while preserving platform-appropriate checkouts. +* text=auto + +# Cross-platform scripts require explicit line endings. +*.sh text eol=lf +*.bash text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +# Binary files must not be modified by Git's text conversion. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.zip binary diff --git a/.github/conventional_commits_labels.json b/.github/conventional_commits_labels.json new file mode 100644 index 0000000..86bdeef --- /dev/null +++ b/.github/conventional_commits_labels.json @@ -0,0 +1,14 @@ +{ + "!": "breaking-change", + "chore": "dependency-update", + "docs": "docs", + "style": "none", + "ci": "build", + "build": "build", + "feat": "new-feature", + "test": "none", + "refactor": "none", + "revert": "bug", + "perf": "enhancement", + "fix": "bug" +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..31f40d2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 + +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "nuget" + directory: "/bindings/csharp" + schedule: + interval: "weekly" + + - package-ecosystem: "pip" + directory: "/bindings/python" + schedule: + interval: "weekly" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..80bc153 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,17 @@ +# .github/release.yml + +changelog: + categories: + - title: Breaking ChangesπŸ’« + labels: + - breaking-change + - title: Exciting new features and enhancementsπŸŽ‰ + labels: + - new-feature + - enhancement + - title: Bug fixes πŸ‘Ύ + labels: + - bug + - title: Other Changes πŸͺ› + labels: + - "*" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6bf41d --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Tree-sitter +node_modules/ +.tree-sitter/ +*.so +*.dll +*.dylib +*.wasm + +# .NET / C# +.vs/ +bin/ +obj/ +TestResults/ +*.user +*.suo + +# Python +__pycache__/ +*.py[cod] +.venv/ +venv/ +.pytest_cache/ +.mypy_cache/ +.pyright/ +*.egg-info/ +dist/ + +# TypeScript / Node +node_modules/ +coverage/ +*.tsbuildinfo + +# IDEs +.idea/ +.vscode/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ec19074 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,66 @@ +## Issues + +Issue titles MUST be descriptive natural-language titles. + +Do NOT use Conventional Commit syntax for issue titles. + +Prefer: + +```text +Map should preserve null values +Add pairwise function +Reduce allocations when mapping arrays +``` + +Avoid: + +```text +fix: preserve null values when mapping arrays +feat: add pairwise function +perf: reduce allocations when mapping arrays +``` + +Every issue MUST have exactly one change-type label: + +* `bug` for a defect +* `new-feature` for new functionality +* `enhancement` for an improvement or refactoring of existing functionality + +The label is determined by the nature of the issue. + +## Skills + +Repository-specific workflows are defined under `.github/skills/`. + +When a task matches an existing skill, read and follow that skill before making changes. + +Skills define task-specific procedures. `AGENTS.md` defines repository-wide rules and takes precedence if a skill contains conflicting Git, worktree, branch, issue, commit, or pull-request instructions. + +## Pull requests + +For every completed implementation: + +1. Push the task branch. +2. Create a GitHub pull request targeting `main`. +3. Use a Conventional Commit-style PR title. +4. Include a concise description of the change. +5. Include the relevant tests or validation performed. +6. Link the pull request to the corresponding issue when one exists (use wording `close`). + +Do NOT use `bug`, `new-feature`, or `enhancement` labels on the pull request unless explicitly requested. + +## Completion criteria + +A coding task is complete only when: + +* implementation was performed in the task's dedicated worktree; +* for a new task, the branch was created from the latest `origin/main`; +* the relevant tests have been run; +* all intended changes are committed; +* commit messages follow Conventional Commits; +* the branch has been pushed; +* a pull request targeting `main` has been created; +* the PR title follows Conventional Commits; +* the corresponding issue has the appropriate `bug`, `new-feature`, or `enhancement` label; +* the pull request is linked to the issue when one exists; +* the worktree is clean. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b4599d2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# How to contribute + +Third-party patches are essential for keeping **Expressif-Syntax** great. We want to keep it as easy as possible to contribute changes. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. + +We love pull requests from everyone. By participating in this project, you agree to abide by the thoughtbot [code of conduct]. + +[code of conduct]: https://thoughtbot.com/open-source-code-of-conduct + +## Getting Started + +* Make sure you have a [GitHub account](https://github.com/signup/free) (It's free!) +* Read the documentation. +* Search in the issues if your problem hasn't already be submitted. +* Submit an issue (assuming one does not already exist). + * Clearly describe the issue including steps to reproduce when it is a bug. + * Specify the version on which this bug has been found. +* Fork the repository on GitHub + +## Making Changes + +* Create a topic branch from where you want to base your work. + * This is usually the master branch. + * Only target release branches if you are certain your fix must be on that + branch. + * To quickly create a topic branch based on main; `git checkout -b + feature/my_contribution main`. Please avoid working directly on the + `main` branch. +* Make commits of logical units. +* Make sure your commit messages are explicit. +* Make sure you have added the necessary tests for your changes. +* If possible, run _all_ the tests (for _all_ .NET versions supported) to assure nothing else was accidentally broken. +* Check the status of your build on appVeyor. +* If something is broken, try to fix it. + +## Making Trivial Changes + +### Documentation + +For changes of a trivial nature to comments and documentation, it is not necessary to create an issue. In this case, it is +appropriate to start the first line of a commit with '(doc)'. + +## Submitting Changes + +* Push your changes to a topic branch in your fork of the repository. +* Submit a pull request to the repository Expressif (Seddryck/Expressif). +* Update your issue to mark that you have submitted code and are ready for it to be reviewed. Include a link to the pull request in the ticket. +* At this point, you're waiting on us. We like to, at least, comment on pull requests within a few business days. We may suggest some changes or improvements or alternatives. + +# Some things that will increase the chance that + +## Your issue will be quickly answered: + +* Write a good description of your problem in the issue ticket + * If you've a problem using Expressif, post your test (not a screen capture) and explain what you're trying to achieve. Don't forget to post the error (full stack) that you're receiving! + * If you'd like to suggest a feature, explain why you think it will be valuable and suggest a syntax. + +## Your pull request is accepted: + +* Link your pull request to an issue that is well documented. +* Write unit tests. +* Write the user documentation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa25b4f --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# Expressif.Syntax + +Expressif.Syntax provides the [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) parser for the [Expressif](https://github.com/Seddryck/Expressif) expression language, together with bindings for supported programming languages. + +The parser defines the concrete syntax of Expressif independently from its runtime implementations. It is intended to provide a common syntax foundation for the C#, Python and TypeScript implementations of Expressif, as well as editor tooling and language-server support. + +[About](#about) | [Repository structure](#repository-structure) | [Development](#development) + +## About + +**Project:** [![Expressif](https://img.shields.io/badge/Expressif-language-fe762d.svg)](https://github.com/Seddryck/Expressif) +[![Tree-sitter](https://img.shields.io/badge/parser-Tree--sitter-6a9f58.svg)](https://tree-sitter.github.io/tree-sitter/) + +**Releases:** [![GitHub Release](https://img.shields.io/github/v/release/Seddryck/Expressif.Syntax)](https://github.com/Seddryck/Expressif.Syntax/releases/latest) +[![GitHub Release Date](https://img.shields.io/github/release-date/Seddryck/Expressif.Syntax.svg)](https://github.com/Seddryck/Expressif.Syntax/releases/latest) +[![licence badge](https://img.shields.io/badge/License-Apache%202.0-yellow.svg)](https://github.com/Seddryck/Expressif.Syntax/blob/main/LICENSE) + +**Dev. activity:** [![GitHub last commit](https://img.shields.io/github/last-commit/Seddryck/Expressif.Syntax.svg)](https://github.com/Seddryck/Expressif.Syntax/commits) +![Still maintained](https://img.shields.io/maintenance/yes/2026.svg) +![GitHub commit activity](https://img.shields.io/github/commit-activity/y/Seddryck/Expressif.Syntax) + +**Status:** [![stars badge](https://img.shields.io/github/stars/Seddryck/Expressif.Syntax.svg)](https://github.com/Seddryck/Expressif.Syntax/stargazers) +[![Bugs badge](https://img.shields.io/github/issues/Seddryck/Expressif.Syntax/bug.svg?color=red\&label=Bugs)](https://github.com/Seddryck/Expressif.Syntax/issues?q=is%3Aissue+is%3Aopen+label%3Abug) +[![Features badge](https://img.shields.io/github/issues/Seddryck/Expressif.Syntax/new-feature.svg?color=purple\&label=Feature%20requests)](https://github.com/Seddryck/Expressif.Syntax/issues?q=is%3Aissue+is%3Aopen+label%3Anew-feature) + +## Purpose + +Expressif.Syntax separates the syntax of the Expressif language from its runtime semantics. + +The Tree-sitter grammar parses source text into a syntax tree while preserving syntactic constructs such as shorthands. Language-specific bindings can then translate this tree into the semantic representation expected by an Expressif implementation. + +For example: + +```text +.foo +``` + +is represented syntactically as a field-access construct, while: + +```text +field(foo) +``` + +is represented as a regular function call. Both can later be bound to the same semantic operation: + +```text +field(foo) +``` + +This separation allows the same parser to support: + +* Expressif for C# +* Expressif for Python +* Expressif for TypeScript +* language servers +* syntax highlighting and other editor tooling + +## Repository structure + +```text +. +β”œβ”€β”€ grammar.js +β”œβ”€β”€ tree-sitter.json +β”œβ”€β”€ package.json +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ parser.c +β”‚ β”œβ”€β”€ grammar.json +β”‚ └── node-types.json +β”œβ”€β”€ bindings/ +β”‚ β”œβ”€β”€ csharp/ +β”‚ β”œβ”€β”€ python/ +β”‚ └── typescript/ +β”œβ”€β”€ queries/ +β”‚ └── highlights.scm +└── test/ +``` + +`grammar.js` is the source definition of the Expressif grammar. + +The files under `src/` are generated by Tree-sitter and are committed to source control so consumers do not need the Tree-sitter CLI to build the parser. + +Language-specific integration is located under `bindings/`. + +## Development + +Install the dependencies: + +```sh +npm install +``` + +Generate the parser: + +```sh +npx tree-sitter generate +``` + +Run the grammar tests: + +```sh +npx tree-sitter test +``` + +The grammar should remain independent from the Expressif function catalogue. Parsing determines the syntactic structure of an expression; resolution of functions, predicates, accumulators and their accepted arguments belongs to the language-specific semantic binding layer. + +## Related projects + +* [Expressif](https://github.com/Seddryck/Expressif) β€” C# implementation and reference project +* [Expressif documentation](https://seddryck.github.io/Expressif/) β€” language documentation diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..92e3419 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Reporting a Vulnerability + +If you've found a security issue in Expressif-Parser, you can submit your report with [GitHub's private vulnerabilty reporting feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) on the corresponding repository. + +Please include as much information as possible in your report to better help us understand and resolve the issue: + +- Where the security issue exists +- The type of issue (ex. SQL injection, cross-site scripting, missing authorization, etc.) +- Full paths or links to the source files where the security issue exists, if possible +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof of concept or exploit code, if available From aa5f8f67197fd06f9c0252ff199319e76e2f48ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20L=2E=20Charlier?= Date: Sun, 9 Aug 2026 00:57:18 +0200 Subject: [PATCH 2/6] chore: initialize Tree-sitter project with C# CI --- .editorconfig | 15 + .gitattributes | 3 + .github/workflows/ci.yml | 80 +++++ .gitignore | 4 +- CMakeLists.txt | 76 ++++ Makefile | 116 ++++++ binding.gyp | 35 ++ bindings/c/tree-sitter-expressif.pc.in | 10 + .../c/tree_sitter/tree-sitter-expressif.h | 16 + bindings/node/binding.cc | 19 + bindings/node/binding_test.js | 11 + bindings/node/index.d.ts | 60 ++++ bindings/node/index.js | 37 ++ bindings/python/tests/test_binding.py | 12 + .../python/tree_sitter_expressif/__init__.py | 43 +++ .../python/tree_sitter_expressif/__init__.pyi | 17 + .../python/tree_sitter_expressif/binding.c | 35 ++ .../python/tree_sitter_expressif/py.typed | 0 grammar.js | 20 ++ package-lock.json | 67 ++++ package.json | 35 ++ pyproject.toml | 29 ++ queries/highlights.scm | 1 + setup.py | 78 ++++ src/grammar.json | 29 ++ src/node-types.json | 22 ++ src/parser.c | 211 +++++++++++ src/tree_sitter/alloc.h | 54 +++ src/tree_sitter/array.h | 336 ++++++++++++++++++ src/tree_sitter/parser.h | 286 +++++++++++++++ test/corpus/expressions.txt | 10 + tree-sitter.json | 37 ++ 32 files changed, 1803 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 binding.gyp create mode 100644 bindings/c/tree-sitter-expressif.pc.in create mode 100644 bindings/c/tree_sitter/tree-sitter-expressif.h create mode 100644 bindings/node/binding.cc create mode 100644 bindings/node/binding_test.js create mode 100644 bindings/node/index.d.ts create mode 100644 bindings/node/index.js create mode 100644 bindings/python/tests/test_binding.py create mode 100644 bindings/python/tree_sitter_expressif/__init__.py create mode 100644 bindings/python/tree_sitter_expressif/__init__.pyi create mode 100644 bindings/python/tree_sitter_expressif/binding.c create mode 100644 bindings/python/tree_sitter_expressif/py.typed create mode 100644 grammar.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pyproject.toml create mode 100644 queries/highlights.scm create mode 100644 setup.py create mode 100644 src/grammar.json create mode 100644 src/node-types.json create mode 100644 src/parser.c create mode 100644 src/tree_sitter/alloc.h create mode 100644 src/tree_sitter/array.h create mode 100644 src/tree_sitter/parser.h create mode 100644 test/corpus/expressions.txt create mode 100644 tree-sitter.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7cce79b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.{bat,cmd,ps1}] +end_of_line = crlf diff --git a/.gitattributes b/.gitattributes index 8f4a379..e974666 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,3 +16,6 @@ *.ico binary *.pdf binary *.zip binary + +# Tree-sitter parser artifacts are generated from grammar.js. +src/** linguist-generated=true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5c89336 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + parser: + name: Test Tree-sitter parser + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Set up Tree-sitter + uses: tree-sitter/setup-action/cli@v2 + + - name: Test parser + uses: tree-sitter/parser-test-action@v3 + + csharp: + name: Build and test C# binding + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Set up .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + + - name: Discover C# projects + id: csharp-projects + shell: pwsh + run: | + $projects = @(Get-ChildItem bindings/csharp -Recurse -Filter *.csproj) + "found=$($projects.Count -gt 0)" >> $env:GITHUB_OUTPUT + if ($projects.Count -eq 0) { + Write-Host "No C# projects exist yet; skipping .NET validation." + } + + - name: Restore C# projects + if: steps.csharp-projects.outputs.found == 'True' + shell: pwsh + run: | + Get-ChildItem bindings/csharp -Recurse -Filter *.csproj | ForEach-Object { + dotnet restore $_.FullName + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + + - name: Build C# projects + if: steps.csharp-projects.outputs.found == 'True' + shell: pwsh + run: | + Get-ChildItem bindings/csharp -Recurse -Filter *.csproj | ForEach-Object { + dotnet build $_.FullName --configuration Release --no-restore + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + + - name: Test C# projects + if: steps.csharp-projects.outputs.found == 'True' + shell: pwsh + run: | + Get-ChildItem bindings/csharp -Recurse -Filter *.csproj | ForEach-Object { + dotnet test $_.FullName --configuration Release --no-build + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } diff --git a/.gitignore b/.gitignore index f6bf41d..716270f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ dist/ node_modules/ coverage/ *.tsbuildinfo +build/ +*.node # IDEs .idea/ @@ -39,4 +41,4 @@ coverage/ Thumbs.db # Logs -*.log \ No newline at end of file +*.log diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2dc815c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.13) + +project(tree-sitter-expressif + VERSION "0.1.0" + DESCRIPTION "Tree-sitter parser for the Expressif expression language" + HOMEPAGE_URL "https://github.com/Seddryck/Expressif.Syntax" + 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 15 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() + +include(GNUInstallDirs) + +find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI") + +add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" + "${CMAKE_CURRENT_SOURCE_DIR}/src/node-types.json" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/grammar.js" + COMMAND "${TREE_SITTER_CLI}" generate grammar.js --no-parser + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating grammar.json") + +add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" + BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/parser.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/alloc.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/array.h" + 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-expressif src/parser.c) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c) + target_sources(tree-sitter-expressif PRIVATE src/scanner.c) +endif() +target_include_directories(tree-sitter-expressif + PRIVATE src + INTERFACE $ + $) + +target_compile_definitions(tree-sitter-expressif PRIVATE + $<$:TREE_SITTER_REUSE_ALLOCATOR> + $<$:TREE_SITTER_DEBUG>) + +set_target_properties(tree-sitter-expressif + PROPERTIES + C_STANDARD 11 + POSITION_INDEPENDENT_CODE ON + SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}" + DEFINE_SYMBOL "") + +configure_file(bindings/c/tree-sitter-expressif.pc.in + "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-expressif.pc" @ONLY) + +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bindings/c/tree_sitter" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING PATTERN "*.h") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-expressif.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +install(TARGETS tree-sitter-expressif + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") + +file(GLOB QUERIES queries/*.scm) +install(FILES ${QUERIES} + DESTINATION "${CMAKE_INSTALL_DATADIR}/tree-sitter/queries/expressif") + +add_custom_target(ts-test "${TREE_SITTER_CLI}" test + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "tree-sitter test") diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..baa9b7b --- /dev/null +++ b/Makefile @@ -0,0 +1,116 @@ +LANGUAGE_NAME := tree-sitter-expressif +HOMEPAGE_URL := https://github.com/Seddryck/Expressif.Syntax +VERSION := 0.1.0 +DESCRIPTION := Tree-sitter parser for the Expressif expression language + +# repository +SRC_DIR := src + +TS ?= tree-sitter + +# install directory layout +PREFIX ?= /usr/local +DATADIR ?= $(PREFIX)/share +INCLUDEDIR ?= $(PREFIX)/include +LIBDIR ?= $(PREFIX)/lib +BINDIR ?= $(PREFIX)/bin +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 +MACHINE := $(shell $(CC) -dumpmachine) + +ifneq ($(findstring darwin,$(MACHINE)),) + 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 ifneq ($(findstring mingw32,$(MACHINE)),) + SOEXT = dll + LINKSHARED += -s -shared -Wl,--out-implib,lib$(LANGUAGE_NAME).dll.a +else + SOEXT = so + SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR) + SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR) + LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER) +ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),) + PCLIBDIR := $(PREFIX)/libdata/pkgconfig +endif +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 + +ifneq ($(findstring mingw32,$(MACHINE)),) +lib$(LANGUAGE_NAME).dll.a: lib$(LANGUAGE_NAME).$(SOEXT) +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)|' $< > $@ + +$(SRC_DIR)/grammar.json: grammar.js + $(TS) generate --no-parser $^ + +$(PARSER): $(SRC_DIR)/grammar.json + $(TS) generate $^ + +install: all + install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/expressif '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)' + install -m644 bindings/c/tree_sitter/$(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) +ifneq ($(findstring mingw32,$(MACHINE)),) + install -d '$(DESTDIR)$(BINDIR)' + install -m755 lib$(LANGUAGE_NAME).dll '$(DESTDIR)$(BINDIR)'/lib$(LANGUAGE_NAME).dll + install -m755 lib$(LANGUAGE_NAME).dll.a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).dll.a +else + install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) + cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) + cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) lib$(LANGUAGE_NAME).$(SOEXT) +endif +ifneq ($(wildcard queries/*.scm),) + install -m644 queries/*.scm '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/expressif +endif + +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 + $(RM) -r '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/expressif + +clean: + $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) lib$(LANGUAGE_NAME).dll.a + +test: + $(TS) test + +.PHONY: all install uninstall clean test diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..421f9c5 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,35 @@ +{ + "targets": [ + { + "target_name": "tree_sitter_expressif_binding", + "dependencies": [ + " + +typedef struct TSLanguage TSLanguage; + +extern "C" TSLanguage *tree_sitter_expressif(); + +// "tree-sitter", "language" hashed with BLAKE2 +const napi_type_tag LANGUAGE_TYPE_TAG = { + 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 +}; + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + auto language = Napi::External::New(env, tree_sitter_expressif()); + language.TypeTag(&LANGUAGE_TYPE_TAG); + exports["language"] = language; + return exports; +} + +NODE_API_MODULE(tree_sitter_expressif_binding, Init) diff --git a/bindings/node/binding_test.js b/bindings/node/binding_test.js new file mode 100644 index 0000000..7a91a84 --- /dev/null +++ b/bindings/node/binding_test.js @@ -0,0 +1,11 @@ +import assert from "node:assert"; +import { test } from "node:test"; +import Parser from "tree-sitter"; + +test("can load grammar", () => { + const parser = new Parser(); + assert.doesNotReject(async () => { + const { default: language } = await import("./index.js"); + parser.setLanguage(language); + }); +}); diff --git a/bindings/node/index.d.ts b/bindings/node/index.d.ts new file mode 100644 index 0000000..473dd42 --- /dev/null +++ b/bindings/node/index.d.ts @@ -0,0 +1,60 @@ +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[]; + }); + +/** + * The tree-sitter language object for this grammar. + * + * @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/Parser.Language.html Parser.Language} + * + * @example + * import Parser from "tree-sitter"; + * import Expressif from "tree-sitter-expressif"; + * + * const parser = new Parser(); + * parser.setLanguage(Expressif); + */ +declare const binding: { + /** + * The inner language object. + * @private + */ + language: unknown; + + /** + * The content of the `node-types.json` file for this grammar. + * + * @see {@linkplain https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types Static Node Types} + */ + nodeTypeInfo: NodeInfo[]; + + /** The syntax highlighting query for this grammar. */ + HIGHLIGHTS_QUERY?: string; + + /** The language injection query for this grammar. */ + INJECTIONS_QUERY?: string; + + /** The local variable query for this grammar. */ + LOCALS_QUERY?: string; + + /** The symbol tagging query for this grammar. */ + TAGS_QUERY?: string; +}; + +export default binding; diff --git a/bindings/node/index.js b/bindings/node/index.js new file mode 100644 index 0000000..6c7f1a8 --- /dev/null +++ b/bindings/node/index.js @@ -0,0 +1,37 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +const root = fileURLToPath(new URL("../..", import.meta.url)); + +const binding = typeof process.versions.bun === "string" + // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time + ? await import(`${root}/prebuilds/${process.platform}-${process.arch}/tree-sitter-expressif.node`) + : (await import("node-gyp-build")).default(root); + +try { + const nodeTypes = await import(`${root}/src/node-types.json`, { with: { type: "json" } }); + binding.nodeTypeInfo = nodeTypes.default; +} catch { } + +const queries = [ + ["HIGHLIGHTS_QUERY", `${root}/queries/highlights.scm`], + ["INJECTIONS_QUERY", `${root}/queries/injections.scm`], + ["LOCALS_QUERY", `${root}/queries/locals.scm`], + ["TAGS_QUERY", `${root}/queries/tags.scm`], +]; + +for (const [prop, path] of queries) { + Object.defineProperty(binding, prop, { + configurable: true, + enumerable: true, + get() { + delete binding[prop]; + try { + binding[prop] = readFileSync(path, "utf8"); + } catch { } + return binding[prop]; + } + }); +} + +export default binding; diff --git a/bindings/python/tests/test_binding.py b/bindings/python/tests/test_binding.py new file mode 100644 index 0000000..e0fb434 --- /dev/null +++ b/bindings/python/tests/test_binding.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from tree_sitter import Language, Parser +import tree_sitter_expressif + + +class TestLanguage(TestCase): + def test_can_load_grammar(self): + try: + Parser(Language(tree_sitter_expressif.language())) + except Exception: + self.fail("Error loading Expressif grammar") diff --git a/bindings/python/tree_sitter_expressif/__init__.py b/bindings/python/tree_sitter_expressif/__init__.py new file mode 100644 index 0000000..1e07968 --- /dev/null +++ b/bindings/python/tree_sitter_expressif/__init__.py @@ -0,0 +1,43 @@ +"""Tree-sitter parser for the Expressif expression language""" + +from importlib.resources import files as _files + +from ._binding import language + + +def _get_query(name, file): + try: + query = _files(f"{__package__}") / file + globals()[name] = query.read_text() + except FileNotFoundError: + globals()[name] = None + return globals()[name] + + +def __getattr__(name): + if name == "HIGHLIGHTS_QUERY": + return _get_query("HIGHLIGHTS_QUERY", "queries/highlights.scm") + if name == "INJECTIONS_QUERY": + return _get_query("INJECTIONS_QUERY", "queries/injections.scm") + if name == "LOCALS_QUERY": + return _get_query("LOCALS_QUERY", "queries/locals.scm") + if name == "TAGS_QUERY": + return _get_query("TAGS_QUERY", "queries/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__", + ]) diff --git a/bindings/python/tree_sitter_expressif/__init__.pyi b/bindings/python/tree_sitter_expressif/__init__.pyi new file mode 100644 index 0000000..5c88ff6 --- /dev/null +++ b/bindings/python/tree_sitter_expressif/__init__.pyi @@ -0,0 +1,17 @@ +from typing import Final +from typing_extensions import CapsuleType + +HIGHLIGHTS_QUERY: Final[str] | None +"""The syntax highlighting query for this grammar.""" + +INJECTIONS_QUERY: Final[str] | None +"""The language injection query for this grammar.""" + +LOCALS_QUERY: Final[str] | None +"""The local variable query for this grammar.""" + +TAGS_QUERY: Final[str] | None +"""The symbol tagging query for this grammar.""" + +def language() -> CapsuleType: + """The tree-sitter language function for this grammar.""" diff --git a/bindings/python/tree_sitter_expressif/binding.c b/bindings/python/tree_sitter_expressif/binding.c new file mode 100644 index 0000000..549a712 --- /dev/null +++ b/bindings/python/tree_sitter_expressif/binding.c @@ -0,0 +1,35 @@ +#include + +typedef struct TSLanguage TSLanguage; + +TSLanguage *tree_sitter_expressif(void); + +static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { + return PyCapsule_New(tree_sitter_expressif(), "tree_sitter.Language", NULL); +} + +static struct PyModuleDef_Slot slots[] = { +#ifdef Py_GIL_DISABLED + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, 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 = 0, + .m_methods = methods, + .m_slots = slots, +}; + +PyMODINIT_FUNC PyInit__binding(void) { + return PyModuleDef_Init(&module); +} diff --git a/bindings/python/tree_sitter_expressif/py.typed b/bindings/python/tree_sitter_expressif/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/grammar.js b/grammar.js new file mode 100644 index 0000000..6c18348 --- /dev/null +++ b/grammar.js @@ -0,0 +1,20 @@ +/** + * @file Tree-sitter grammar for the Expressif expression language + * @author CΓ©dric L. Charlier + * @license Apache-2.0 + */ + +/// +// @ts-check + +export default grammar({ + name: "expressif", + + rules: { + source_file: ($) => repeat($.expression), + + // Initial placeholder rule. The complete Expressif syntax is introduced + // incrementally together with corpus tests. + expression: (_) => "hello", + }, +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..95e8a16 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "tree-sitter-expressif", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tree-sitter-expressif", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "node-addon-api": "^8.9.1", + "node-gyp-build": "^4.8.4" + }, + "devDependencies": { + "tree-sitter": "^0.25.1", + "tree-sitter-cli": "^0.26.12" + } + }, + "node_modules/node-addon-api": { + "version": "8.9.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.1.tgz", + "integrity": "sha512-4eUQWVPCUUUiBjLnHS3cXWeC6ryoPUc0U3rP7IuzapoGbzMqd/r6KKO0clr0b+snQhsrueFEhCZDdK+LK7hxKg==", + "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/tree-sitter": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.25.1.tgz", + "integrity": "sha512-mrcEdkYtHfrK1A6fs3O6FxkBo0Qig5XUXqHhxUOQu0bmPo00QF4XaSx4edpazdHwxnSCjlGKGgIqWdaN4dvTLA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.5.0", + "node-gyp-build": "^4.8.4" + } + }, + "node_modules/tree-sitter-cli": { + "version": "0.26.12", + "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.26.12.tgz", + "integrity": "sha512-rIdMAm6nv75MVFoZ7UTaLVB1OG5h3ZSlCZEp5pne4gOUTalJIkL0A1NlYBg0oEl/d7gvIyc2Y5Jz8m5Xj+NOEw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "tree-sitter": "cli.js" + }, + "engines": { + "node": ">=12.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c11838b --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "tree-sitter-expressif", + "version": "0.1.0", + "description": "Tree-sitter parser for the Expressif expression language", + "type": "module", + "main": "bindings/node", + "types": "bindings/node", + "files": [ + "grammar.js", + "tree-sitter.json", + "binding.gyp", + "prebuilds/**", + "bindings/node/**", + "queries/**", + "src/**" + ], + "scripts": { + "generate": "tree-sitter generate", + "test": "tree-sitter test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Seddryck/Expressif.Syntax.git" + }, + "author": "CΓ©dric L. Charlier ", + "license": "Apache-2.0", + "devDependencies": { + "tree-sitter": "^0.25.1", + "tree-sitter-cli": "^0.26.12" + }, + "dependencies": { + "node-addon-api": "^8.9.1", + "node-gyp-build": "^4.8.4" + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..46541b7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=62.4.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "tree-sitter-expressif" +description = "Tree-sitter parser for the Expressif expression language" +version = "0.1.0" +keywords = ["incremental", "parsing", "tree-sitter", "expressif"] +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Compilers", + "Topic :: Text Processing :: Linguistic", + "Typing :: Typed", +] +authors = [{ name = "CΓ©dric L. Charlier", email = "seddryck@gmail.com" }] +requires-python = ">=3.10" +license.text = "Apache-2.0" +readme = "README.md" + +[project.urls] +Homepage = "https://github.com/Seddryck/Expressif.Syntax" + +[project.optional-dependencies] +core = ["tree-sitter~=0.24"] + +[tool.cibuildwheel] +build = "cp310-*" +build-frontend = "build" diff --git a/queries/highlights.scm b/queries/highlights.scm new file mode 100644 index 0000000..f71b9c4 --- /dev/null +++ b/queries/highlights.scm @@ -0,0 +1 @@ +(expression) @string diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4a7630e --- /dev/null +++ b/setup.py @@ -0,0 +1,78 @@ +from os import path +from sysconfig import get_config_var + +from setuptools import Extension, find_packages, setup +from setuptools.command.build import build +from setuptools.command.build_ext import build_ext +from setuptools.command.egg_info import egg_info +from wheel.bdist_wheel import bdist_wheel + + +class Build(build): + def run(self): + if path.isdir("queries"): + dest = path.join(self.build_lib, "tree_sitter_expressif", "queries") + self.copy_tree("queries", dest) + super().run() + + +class BuildExt(build_ext): + def build_extension(self, ext: Extension): + if self.compiler.compiler_type != "msvc": + ext.extra_compile_args = ["-std=c11", "-fvisibility=hidden"] + else: + ext.extra_compile_args = ["/std:c11", "/utf-8"] + if path.exists("src/scanner.c"): + ext.sources.append("src/scanner.c") + if ext.py_limited_api: + ext.define_macros.append(("Py_LIMITED_API", "0x030A0000")) + super().build_extension(ext) + + +class BdistWheel(bdist_wheel): + def get_tag(self): + python, abi, platform = super().get_tag() + if python.startswith("cp") and not get_config_var("Py_GIL_DISABLED"): + python, abi = "cp310", "abi3" + return python, abi, platform + + +class EggInfo(egg_info): + def find_sources(self): + super().find_sources() + self.filelist.recursive_include("queries", "*.scm") + self.filelist.include("src/tree_sitter/*.h") + self.filelist.include("src/*.c") + + +setup( + packages=find_packages("bindings/python"), + package_dir={"": "bindings/python"}, + package_data={ + "tree_sitter_expressif": ["*.pyi", "py.typed"], + "tree_sitter_expressif.queries": ["*.scm"], + }, + ext_package="tree_sitter_expressif", + ext_modules=[ + Extension( + name="_binding", + sources=[ + "bindings/python/tree_sitter_expressif/binding.c", + "src/parser.c", + ], + define_macros=[ + ("PY_SSIZE_T_CLEAN", None), + ("TREE_SITTER_HIDE_SYMBOLS", None), + ], + include_dirs=["src"], + py_limited_api=not get_config_var("Py_GIL_DISABLED"), + ) + ], + cmdclass={ + "build": Build, + "build_ext": BuildExt, + "bdist_wheel": BdistWheel, + "egg_info": EggInfo, + }, + zip_safe=False +) diff --git a/src/grammar.json b/src/grammar.json new file mode 100644 index 0000000..9f39cab --- /dev/null +++ b/src/grammar.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/grammar.schema.json", + "name": "expressif", + "rules": { + "source_file": { + "type": "REPEAT", + "content": { + "type": "SYMBOL", + "name": "expression" + } + }, + "expression": { + "type": "STRING", + "value": "hello" + } + }, + "extras": [ + { + "type": "PATTERN", + "value": "\\s" + } + ], + "conflicts": [], + "precedences": [], + "externals": [], + "inline": [], + "supertypes": [], + "reserved": {} +} \ No newline at end of file diff --git a/src/node-types.json b/src/node-types.json new file mode 100644 index 0000000..9afe175 --- /dev/null +++ b/src/node-types.json @@ -0,0 +1,22 @@ +[ + { + "type": "source_file", + "named": true, + "root": true, + "fields": {}, + "children": { + "multiple": true, + "required": false, + "types": [ + { + "type": "expression", + "named": true + } + ] + } + }, + { + "type": "expression", + "named": true + } +] \ No newline at end of file diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..af8552d --- /dev/null +++ b/src/parser.c @@ -0,0 +1,211 @@ +/* Automatically @generated by tree-sitter */ + +#include "tree_sitter/parser.h" + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +#define LANGUAGE_VERSION 15 +#define STATE_COUNT 5 +#define LARGE_STATE_COUNT 4 +#define SYMBOL_COUNT 4 +#define ALIAS_COUNT 0 +#define TOKEN_COUNT 2 +#define EXTERNAL_TOKEN_COUNT 0 +#define FIELD_COUNT 0 +#define MAX_ALIAS_SEQUENCE_LENGTH 2 +#define MAX_RESERVED_WORD_SET_SIZE 0 +#define PRODUCTION_ID_COUNT 1 +#define SUPERTYPE_COUNT 0 + +enum ts_symbol_identifiers { + sym_expression = 1, + sym_source_file = 2, + aux_sym_source_file_repeat1 = 3, +}; + +static const char * const ts_symbol_names[] = { + [ts_builtin_sym_end] = "end", + [sym_expression] = "expression", + [sym_source_file] = "source_file", + [aux_sym_source_file_repeat1] = "source_file_repeat1", +}; + +static const TSSymbol ts_symbol_map[] = { + [ts_builtin_sym_end] = ts_builtin_sym_end, + [sym_expression] = sym_expression, + [sym_source_file] = sym_source_file, + [aux_sym_source_file_repeat1] = aux_sym_source_file_repeat1, +}; + +static const TSSymbolMetadata ts_symbol_metadata[] = { + [ts_builtin_sym_end] = { + .visible = false, + .named = true, + }, + [sym_expression] = { + .visible = true, + .named = true, + }, + [sym_source_file] = { + .visible = true, + .named = true, + }, + [aux_sym_source_file_repeat1] = { + .visible = false, + .named = false, + }, +}; + +static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = { + [0] = {0}, +}; + +static const uint16_t ts_non_terminal_alias_map[] = { + 0, +}; + +static const TSStateId ts_primary_state_ids[STATE_COUNT] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, + [4] = 4, +}; + +static bool ts_lex(TSLexer *lexer, TSStateId state) { + START_LEXER(); + eof = lexer->eof(lexer); + switch (state) { + case 0: + if (eof) ADVANCE(5); + if (lookahead == 'h') ADVANCE(1); + if (('\t' <= lookahead && lookahead <= '\r') || + lookahead == ' ') SKIP(0); + END_STATE(); + case 1: + if (lookahead == 'e') ADVANCE(3); + END_STATE(); + case 2: + if (lookahead == 'l') ADVANCE(4); + END_STATE(); + case 3: + if (lookahead == 'l') ADVANCE(2); + END_STATE(); + case 4: + if (lookahead == 'o') ADVANCE(6); + END_STATE(); + case 5: + ACCEPT_TOKEN(ts_builtin_sym_end); + END_STATE(); + case 6: + ACCEPT_TOKEN(sym_expression); + END_STATE(); + default: + return false; + } +} + +static const TSLexerMode ts_lex_modes[STATE_COUNT] = { + [0] = {.lex_state = 0}, + [1] = {.lex_state = 0}, + [2] = {.lex_state = 0}, + [3] = {.lex_state = 0}, + [4] = {.lex_state = 0}, +}; + +static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { + [STATE(0)] = { + [ts_builtin_sym_end] = ACTIONS(1), + [sym_expression] = ACTIONS(1), + }, + [STATE(1)] = { + [sym_source_file] = STATE(4), + [aux_sym_source_file_repeat1] = STATE(2), + [ts_builtin_sym_end] = ACTIONS(3), + [sym_expression] = ACTIONS(5), + }, + [STATE(2)] = { + [aux_sym_source_file_repeat1] = STATE(3), + [ts_builtin_sym_end] = ACTIONS(7), + [sym_expression] = ACTIONS(9), + }, + [STATE(3)] = { + [aux_sym_source_file_repeat1] = STATE(3), + [ts_builtin_sym_end] = ACTIONS(11), + [sym_expression] = ACTIONS(13), + }, +}; + +static const uint16_t ts_small_parse_table[] = { + [0] = 1, + ACTIONS(16), 1, + ts_builtin_sym_end, +}; + +static const uint32_t ts_small_parse_table_map[] = { + [SMALL_STATE(4)] = 0, +}; + +static const TSParseActionEntry ts_parse_actions[] = { + [0] = {.entry = {.count = 0, .reusable = false}}, + [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(), + [3] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 0, 0, 0), + [5] = {.entry = {.count = 1, .reusable = true}}, SHIFT(2), + [7] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 1, 0, 0), + [9] = {.entry = {.count = 1, .reusable = true}}, SHIFT(3), + [11] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), + [13] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(3), + [16] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(), +}; + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef TREE_SITTER_HIDE_SYMBOLS +#define TS_PUBLIC +#elif defined(_WIN32) +#define TS_PUBLIC __declspec(dllexport) +#else +#define TS_PUBLIC __attribute__((visibility("default"))) +#endif + +TS_PUBLIC const TSLanguage *tree_sitter_expressif(void) { + static const TSLanguage language = { + .abi_version = LANGUAGE_VERSION, + .symbol_count = SYMBOL_COUNT, + .alias_count = ALIAS_COUNT, + .token_count = TOKEN_COUNT, + .external_token_count = EXTERNAL_TOKEN_COUNT, + .state_count = STATE_COUNT, + .large_state_count = LARGE_STATE_COUNT, + .production_id_count = PRODUCTION_ID_COUNT, + .supertype_count = SUPERTYPE_COUNT, + .field_count = FIELD_COUNT, + .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, + .parse_table = &ts_parse_table[0][0], + .small_parse_table = ts_small_parse_table, + .small_parse_table_map = ts_small_parse_table_map, + .parse_actions = ts_parse_actions, + .symbol_names = ts_symbol_names, + .symbol_metadata = ts_symbol_metadata, + .public_symbol_map = ts_symbol_map, + .alias_map = ts_non_terminal_alias_map, + .alias_sequences = &ts_alias_sequences[0][0], + .lex_modes = (const void*)ts_lex_modes, + .lex_fn = ts_lex, + .primary_state_ids = ts_primary_state_ids, + .name = "expressif", + .max_reserved_word_set_size = 0, + .metadata = { + .major_version = 0, + .minor_version = 1, + .patch_version = 0, + }, + }; + return &language; +} +#ifdef __cplusplus +} +#endif diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h new file mode 100644 index 0000000..1abdd12 --- /dev/null +++ b/src/tree_sitter/alloc.h @@ -0,0 +1,54 @@ +#ifndef TREE_SITTER_ALLOC_H_ +#define TREE_SITTER_ALLOC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +// 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_ diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h new file mode 100644 index 0000000..eba3da4 --- /dev/null +++ b/src/tree_sitter/array.h @@ -0,0 +1,336 @@ +#ifndef TREE_SITTER_ARRAY_H_ +#define TREE_SITTER_ARRAY_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "./alloc.h" + +#include +#include +#include +#include +#include + +#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) + +#ifdef __cplusplus +#define _array__cast(self, expr) (decltype((self)->contents))(expr) +#else +#define _array__cast(self, expr) (expr) +#endif + +/// 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) \ + ((self)->contents = _array__cast(self, _array__reserve( \ + (void *)(self)->contents, &(self)->capacity, \ + 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) \ + do { \ + if ((self)->contents) ts_free((self)->contents); \ + (self)->contents = NULL; \ + (self)->size = 0; \ + (self)->capacity = 0; \ + } while (0) + +/// Push a new `element` onto the end of the array. +#define array_push(self, element) \ + do { \ + (self)->contents = _array__cast(self, _array__grow( \ + (void *)(self)->contents, (self)->size, &(self)->capacity, \ + 1, array_elem_size(self) \ + )); \ + (self)->contents[(self)->size++] = (element); \ + } while(0) + +/// Increase the array's size by `count` elements. +/// New elements are zero-initialized. +#define array_grow_by(self, count) \ + do { \ + if ((count) == 0) break; \ + (self)->contents = _array__cast(self, _array__grow( \ + (self)->contents, (self)->size, &(self)->capacity, \ + 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, other_contents) \ + ((self)->contents = _array__cast(self, _array__splice( \ + (void*)(self)->contents, &(self)->size, &(self)->capacity, \ + array_elem_size(self), (self)->size, 0, count, other_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) \ + ((self)->contents = _array__cast(self, _array__splice( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + 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) \ + ((self)->contents = _array__cast(self, _array__splice( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + 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((void *)(self)->contents, &(self)->size, 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) \ + ((self)->contents = _array__cast(self, _array__assign( \ + (void *)(self)->contents, &(self)->size, &(self)->capacity, \ + (const void *)(other)->contents, (other)->size, array_elem_size(self) \ + ))) + +/// Swap one array with another +#define array_swap(self, other) \ + do { \ + void *_array_swap_tmp = (void *)(self)->contents; \ + (self)->contents = (other)->contents; \ + (other)->contents = _array__cast(other, _array_swap_tmp); \ + _array__swap(&(self)->size, &(self)->capacity, \ + &(other)->size, &(other)->capacity); \ + } while (0) + +/// 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 + +// Pointers to individual `Array` fields (rather than the entire `Array` itself) +// are passed to the various `_array__*` functions below to address strict aliasing +// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`. +// +// The `Array` type itself was not altered as a solution in order to avoid breakage +// with existing consumers (in particular, parsers with external scanners). + +/// This is not what you're looking for, see `array_erase`. +static inline void _array__erase(void* self_contents, uint32_t *size, + size_t element_size, uint32_t index) { + assert(index < *size); + char *contents = (char *)self_contents; + memmove(contents + index * element_size, contents + (index + 1) * element_size, + (*size - index - 1) * element_size); + (*size)--; +} + +/// This is not what you're looking for, see `array_reserve`. +static inline void *_array__reserve(void *contents, uint32_t *capacity, + size_t element_size, uint32_t new_capacity) { + void *new_contents = contents; + if (new_capacity > *capacity) { + if (contents) { + new_contents = ts_realloc(contents, new_capacity * element_size); + } else { + new_contents = ts_malloc(new_capacity * element_size); + } + *capacity = new_capacity; + } + return new_contents; +} + +/// This is not what you're looking for, see `array_assign`. +static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity, + const void *other_contents, uint32_t other_size, size_t element_size) { + void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size); + *self_size = other_size; + memcpy(new_contents, other_contents, *self_size * element_size); + return new_contents; +} + +/// This is not what you're looking for, see `array_swap`. +static inline void _array__swap(uint32_t *self_size, uint32_t *self_capacity, + uint32_t *other_size, uint32_t *other_capacity) { + uint32_t tmp_size = *self_size; + uint32_t tmp_capacity = *self_capacity; + *self_size = *other_size; + *self_capacity = *other_capacity; + *other_size = tmp_size; + *other_capacity = tmp_capacity; +} + +/// This is not what you're looking for, see `array_push` or `array_grow_by`. +static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity, + uint32_t count, size_t element_size) { + void *new_contents = contents; + uint32_t new_size = size + count; + if (new_size > *capacity) { + uint32_t new_capacity = *capacity * 2; + if (new_capacity < 8) new_capacity = 8; + if (new_capacity < new_size) new_capacity = new_size; + new_contents = _array__reserve(contents, capacity, element_size, new_capacity); + } + return new_contents; +} + +/// This is not what you're looking for, see `array_splice`. +static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity, + size_t element_size, + uint32_t index, uint32_t old_count, + uint32_t new_count, const void *elements) { + uint32_t new_size = *size + new_count - old_count; + uint32_t old_end = index + old_count; + uint32_t new_end = index + new_count; + assert(old_end <= *size); + + void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size); + + char *contents = (char *)new_contents; + if (*size > old_end) { + memmove( + contents + new_end * element_size, + contents + old_end * element_size, + (*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 + ); + } + } + *size += new_count - old_count; + + return new_contents; +} + +/// 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_ diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h new file mode 100644 index 0000000..858107d --- /dev/null +++ b/src/tree_sitter/parser.h @@ -0,0 +1,286 @@ +#ifndef TREE_SITTER_PARSER_H_ +#define TREE_SITTER_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#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; +typedef struct TSLanguageMetadata { + uint8_t major_version; + uint8_t minor_version; + uint8_t patch_version; +} TSLanguageMetadata; +#endif + +typedef struct { + TSFieldId field_id; + uint8_t child_index; + bool inherited; +} TSFieldMapEntry; + +// Used to index the field and supertype maps. +typedef struct { + uint16_t index; + uint16_t length; +} TSMapSlice; + +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 struct { + uint16_t lex_state; + uint16_t external_lex_state; + uint16_t reserved_word_set_id; +} TSLexerMode; + +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 abi_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 TSMapSlice *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 TSLexerMode *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; + const char *name; + const TSSymbol *reserved_words; + uint16_t max_reserved_word_set_size; + uint32_t supertype_count; + const TSSymbol *supertype_symbols; + const TSMapSlice *supertype_map_slices; + const TSSymbol *supertype_map_entries; + TSLanguageMetadata metadata; +}; + +static inline bool set_contains(const 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; + const 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; + } + const 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_ diff --git a/test/corpus/expressions.txt b/test/corpus/expressions.txt new file mode 100644 index 0000000..94c6fda --- /dev/null +++ b/test/corpus/expressions.txt @@ -0,0 +1,10 @@ +================== +Placeholder expression +================== + +hello + +--- + +(source_file + (expression)) diff --git a/tree-sitter.json b/tree-sitter.json new file mode 100644 index 0000000..a82fcc5 --- /dev/null +++ b/tree-sitter.json @@ -0,0 +1,37 @@ +{ + "grammars": [ + { + "name": "expressif", + "camelcase": "Expressif", + "scope": "source.expressif", + "path": ".", + "file-types": [ + "expr" + ], + "highlights": "queries/highlights.scm" + } + ], + "metadata": { + "version": "0.1.0", + "license": "Apache-2.0", + "description": "Tree-sitter parser for the Expressif expression language", + "authors": [ + { + "name": "CΓ©dric L. Charlier", + "email": "seddryck@gmail.com" + } + ], + "links": { + "repository": "https://github.com/Seddryck/Expressif.Syntax" + } + }, + "bindings": { + "c": true, + "go": false, + "node": true, + "python": true, + "rust": false, + "swift": false, + "zig": false + } +} From d01710c089f95ede29c9acc59f1eb2ba77421b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20L=2E=20Charlier?= Date: Sun, 9 Aug 2026 01:07:03 +0200 Subject: [PATCH 3/6] feat: implement basic expression grammar --- grammar.js | 80 ++- queries/highlights.scm | 16 +- src/grammar.json | 329 ++++++++++- src/node-types.json | 296 +++++++++- src/parser.c | 1028 +++++++++++++++++++++++++++++++++-- test/corpus/expressions.txt | 305 ++++++++++- test/corpus/invalid.txt | 250 +++++++++ 7 files changed, 2236 insertions(+), 68 deletions(-) create mode 100644 test/corpus/invalid.txt diff --git a/grammar.js b/grammar.js index 6c18348..1b68b46 100644 --- a/grammar.js +++ b/grammar.js @@ -10,11 +10,83 @@ export default grammar({ name: "expressif", + extras: ($) => [/[\s\uFEFF\u2060\u200B]/], + rules: { - source_file: ($) => repeat($.expression), + source_file: ($) => $.root_expression, + + root_expression: ($) => choice($.open_expression, $.closed_expression), + + open_expression: ($) => seq( + $.expression, + repeat(seq("|", $.expression)), + ), + + closed_expression: ($) => seq( + $.value, + optional(seq("|", $.expression, repeat(seq("|", $.expression)))), + ), + + expression: ($) => $.function_call, + + function_call: ($) => seq( + field("name", $.function_name), + optional(seq("(", optional($.argument_list), ")")), + ), + + function_name: (_) => /[A-Za-z]+(?:-[A-Za-z]+)*/, + + argument_list: ($) => seq( + $.positional_argument, + repeat(seq(",", $.positional_argument)), + ), + + positional_argument: ($) => $.value, + + value: ($) => choice( + $.numeric_literal, + $.boolean_literal, + $.quoted_literal, + $.temporal_literal, + ), + + numeric_literal: (_) => /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?/, + + boolean_literal: (_) => choice("#true", "#false"), + + quoted_literal: ($) => choice( + $.double_quoted_literal, + $.backtick_quoted_literal, + ), + + double_quoted_literal: ($) => seq( + '"', + repeat(choice($.double_quoted_content, $.escape_sequence)), + '"', + ), + + double_quoted_content: (_) => token.immediate(/[^"\\\r\n]+/), + + escape_sequence: (_) => token.immediate(/\\["\\]/), + + backtick_quoted_literal: ($) => seq( + "`", + optional($.backtick_quoted_content), + "`", + ), + + backtick_quoted_content: (_) => token.immediate(/[^`\r\n]+/), + + temporal_literal: ($) => choice( + $.date_literal, + $.date_time_literal, + $.time_literal, + ), + + date_literal: (_) => /#"[0-9]{4}-[0-9]{2}-[0-9]{2}"/, + + date_time_literal: (_) => /#"[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}"/, - // Initial placeholder rule. The complete Expressif syntax is introduced - // incrementally together with corpus tests. - expression: (_) => "hello", + time_literal: (_) => /#"[0-9]{2}:[0-9]{2}:[0-9]{2}"/, }, }); diff --git a/queries/highlights.scm b/queries/highlights.scm index f71b9c4..e533c76 100644 --- a/queries/highlights.scm +++ b/queries/highlights.scm @@ -1 +1,15 @@ -(expression) @string +(function_name) @function + +(numeric_literal) @number +(boolean_literal) @boolean + +[ + (double_quoted_literal) + (backtick_quoted_literal) +] @string + +[ + (date_literal) + (date_time_literal) + (time_literal) +] @string.special diff --git a/src/grammar.json b/src/grammar.json index 9f39cab..d540cf4 100644 --- a/src/grammar.json +++ b/src/grammar.json @@ -3,21 +3,336 @@ "name": "expressif", "rules": { "source_file": { - "type": "REPEAT", + "type": "SYMBOL", + "name": "root_expression" + }, + "root_expression": { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "open_expression" + }, + { + "type": "SYMBOL", + "name": "closed_expression" + } + ] + }, + "open_expression": { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "expression" + }, + { + "type": "REPEAT", + "content": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "|" + }, + { + "type": "SYMBOL", + "name": "expression" + } + ] + } + } + ] + }, + "closed_expression": { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "value" + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "|" + }, + { + "type": "SYMBOL", + "name": "expression" + }, + { + "type": "REPEAT", + "content": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "|" + }, + { + "type": "SYMBOL", + "name": "expression" + } + ] + } + } + ] + }, + { + "type": "BLANK" + } + ] + } + ] + }, + "expression": { + "type": "SYMBOL", + "name": "function_call" + }, + "function_call": { + "type": "SEQ", + "members": [ + { + "type": "FIELD", + "name": "name", + "content": { + "type": "SYMBOL", + "name": "function_name" + } + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "(" + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "argument_list" + }, + { + "type": "BLANK" + } + ] + }, + { + "type": "STRING", + "value": ")" + } + ] + }, + { + "type": "BLANK" + } + ] + } + ] + }, + "function_name": { + "type": "PATTERN", + "value": "[A-Za-z]+(?:-[A-Za-z]+)*" + }, + "argument_list": { + "type": "SEQ", + "members": [ + { + "type": "SYMBOL", + "name": "positional_argument" + }, + { + "type": "REPEAT", + "content": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "," + }, + { + "type": "SYMBOL", + "name": "positional_argument" + } + ] + } + } + ] + }, + "positional_argument": { + "type": "SYMBOL", + "name": "value" + }, + "value": { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "numeric_literal" + }, + { + "type": "SYMBOL", + "name": "boolean_literal" + }, + { + "type": "SYMBOL", + "name": "quoted_literal" + }, + { + "type": "SYMBOL", + "name": "temporal_literal" + } + ] + }, + "numeric_literal": { + "type": "PATTERN", + "value": "-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?" + }, + "boolean_literal": { + "type": "CHOICE", + "members": [ + { + "type": "STRING", + "value": "#true" + }, + { + "type": "STRING", + "value": "#false" + } + ] + }, + "quoted_literal": { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "double_quoted_literal" + }, + { + "type": "SYMBOL", + "name": "backtick_quoted_literal" + } + ] + }, + "double_quoted_literal": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "\"" + }, + { + "type": "REPEAT", + "content": { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "double_quoted_content" + }, + { + "type": "SYMBOL", + "name": "escape_sequence" + } + ] + } + }, + { + "type": "STRING", + "value": "\"" + } + ] + }, + "double_quoted_content": { + "type": "IMMEDIATE_TOKEN", "content": { - "type": "SYMBOL", - "name": "expression" + "type": "PATTERN", + "value": "[^\"\\\\\\r\\n]+" } }, - "expression": { - "type": "STRING", - "value": "hello" + "escape_sequence": { + "type": "IMMEDIATE_TOKEN", + "content": { + "type": "PATTERN", + "value": "\\\\[\"\\\\]" + } + }, + "backtick_quoted_literal": { + "type": "SEQ", + "members": [ + { + "type": "STRING", + "value": "`" + }, + { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "backtick_quoted_content" + }, + { + "type": "BLANK" + } + ] + }, + { + "type": "STRING", + "value": "`" + } + ] + }, + "backtick_quoted_content": { + "type": "IMMEDIATE_TOKEN", + "content": { + "type": "PATTERN", + "value": "[^`\\r\\n]+" + } + }, + "temporal_literal": { + "type": "CHOICE", + "members": [ + { + "type": "SYMBOL", + "name": "date_literal" + }, + { + "type": "SYMBOL", + "name": "date_time_literal" + }, + { + "type": "SYMBOL", + "name": "time_literal" + } + ] + }, + "date_literal": { + "type": "PATTERN", + "value": "#\"[0-9]{4}-[0-9]{2}-[0-9]{2}\"" + }, + "date_time_literal": { + "type": "PATTERN", + "value": "#\"[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}\"" + }, + "time_literal": { + "type": "PATTERN", + "value": "#\"[0-9]{2}:[0-9]{2}:[0-9]{2}\"" } }, "extras": [ { "type": "PATTERN", - "value": "\\s" + "value": "[\\s\\uFEFF\\u2060\\u200B]" } ], "conflicts": [], diff --git a/src/node-types.json b/src/node-types.json index 9afe175..171c1d5 100644 --- a/src/node-types.json +++ b/src/node-types.json @@ -1,22 +1,314 @@ [ { - "type": "source_file", + "type": "argument_list", "named": true, - "root": true, "fields": {}, "children": { "multiple": true, + "required": true, + "types": [ + { + "type": "positional_argument", + "named": true + } + ] + } + }, + { + "type": "backtick_quoted_literal", + "named": true, + "fields": {}, + "children": { + "multiple": false, "required": false, + "types": [ + { + "type": "backtick_quoted_content", + "named": true + } + ] + } + }, + { + "type": "boolean_literal", + "named": true, + "fields": {} + }, + { + "type": "closed_expression", + "named": true, + "fields": {}, + "children": { + "multiple": true, + "required": true, "types": [ { "type": "expression", "named": true + }, + { + "type": "value", + "named": true + } + ] + } + }, + { + "type": "double_quoted_literal", + "named": true, + "fields": {}, + "children": { + "multiple": true, + "required": false, + "types": [ + { + "type": "double_quoted_content", + "named": true + }, + { + "type": "escape_sequence", + "named": true } ] } }, { "type": "expression", + "named": true, + "fields": {}, + "children": { + "multiple": false, + "required": true, + "types": [ + { + "type": "function_call", + "named": true + } + ] + } + }, + { + "type": "function_call", + "named": true, + "fields": { + "name": { + "multiple": false, + "required": true, + "types": [ + { + "type": "function_name", + "named": true + } + ] + } + }, + "children": { + "multiple": false, + "required": false, + "types": [ + { + "type": "argument_list", + "named": true + } + ] + } + }, + { + "type": "open_expression", + "named": true, + "fields": {}, + "children": { + "multiple": true, + "required": true, + "types": [ + { + "type": "expression", + "named": true + } + ] + } + }, + { + "type": "positional_argument", + "named": true, + "fields": {}, + "children": { + "multiple": false, + "required": true, + "types": [ + { + "type": "value", + "named": true + } + ] + } + }, + { + "type": "quoted_literal", + "named": true, + "fields": {}, + "children": { + "multiple": false, + "required": true, + "types": [ + { + "type": "backtick_quoted_literal", + "named": true + }, + { + "type": "double_quoted_literal", + "named": true + } + ] + } + }, + { + "type": "root_expression", + "named": true, + "fields": {}, + "children": { + "multiple": false, + "required": true, + "types": [ + { + "type": "closed_expression", + "named": true + }, + { + "type": "open_expression", + "named": true + } + ] + } + }, + { + "type": "source_file", + "named": true, + "root": true, + "fields": {}, + "children": { + "multiple": false, + "required": true, + "types": [ + { + "type": "root_expression", + "named": true + } + ] + } + }, + { + "type": "temporal_literal", + "named": true, + "fields": {}, + "children": { + "multiple": false, + "required": true, + "types": [ + { + "type": "date_literal", + "named": true + }, + { + "type": "date_time_literal", + "named": true + }, + { + "type": "time_literal", + "named": true + } + ] + } + }, + { + "type": "value", + "named": true, + "fields": {}, + "children": { + "multiple": false, + "required": true, + "types": [ + { + "type": "boolean_literal", + "named": true + }, + { + "type": "numeric_literal", + "named": true + }, + { + "type": "quoted_literal", + "named": true + }, + { + "type": "temporal_literal", + "named": true + } + ] + } + }, + { + "type": "\"", + "named": false + }, + { + "type": "#false", + "named": false + }, + { + "type": "#true", + "named": false + }, + { + "type": "(", + "named": false + }, + { + "type": ")", + "named": false + }, + { + "type": ",", + "named": false + }, + { + "type": "`", + "named": false + }, + { + "type": "backtick_quoted_content", + "named": true + }, + { + "type": "date_literal", + "named": true + }, + { + "type": "date_time_literal", + "named": true + }, + { + "type": "double_quoted_content", + "named": true + }, + { + "type": "escape_sequence", + "named": true + }, + { + "type": "function_name", + "named": true + }, + { + "type": "numeric_literal", + "named": true + }, + { + "type": "time_literal", "named": true + }, + { + "type": "|", + "named": false } ] \ No newline at end of file diff --git a/src/parser.c b/src/parser.c index af8552d..3f888e6 100644 --- a/src/parser.c +++ b/src/parser.c @@ -7,36 +7,126 @@ #endif #define LANGUAGE_VERSION 15 -#define STATE_COUNT 5 -#define LARGE_STATE_COUNT 4 -#define SYMBOL_COUNT 4 +#define STATE_COUNT 39 +#define LARGE_STATE_COUNT 2 +#define SYMBOL_COUNT 34 #define ALIAS_COUNT 0 -#define TOKEN_COUNT 2 +#define TOKEN_COUNT 17 #define EXTERNAL_TOKEN_COUNT 0 -#define FIELD_COUNT 0 -#define MAX_ALIAS_SEQUENCE_LENGTH 2 +#define FIELD_COUNT 1 +#define MAX_ALIAS_SEQUENCE_LENGTH 4 #define MAX_RESERVED_WORD_SET_SIZE 0 -#define PRODUCTION_ID_COUNT 1 +#define PRODUCTION_ID_COUNT 2 #define SUPERTYPE_COUNT 0 enum ts_symbol_identifiers { - sym_expression = 1, - sym_source_file = 2, - aux_sym_source_file_repeat1 = 3, + anon_sym_PIPE = 1, + anon_sym_LPAREN = 2, + anon_sym_RPAREN = 3, + sym_function_name = 4, + anon_sym_COMMA = 5, + sym_numeric_literal = 6, + anon_sym_POUNDtrue = 7, + anon_sym_POUNDfalse = 8, + anon_sym_DQUOTE = 9, + sym_double_quoted_content = 10, + sym_escape_sequence = 11, + anon_sym_BQUOTE = 12, + sym_backtick_quoted_content = 13, + sym_date_literal = 14, + sym_date_time_literal = 15, + sym_time_literal = 16, + sym_source_file = 17, + sym_root_expression = 18, + sym_open_expression = 19, + sym_closed_expression = 20, + sym_expression = 21, + sym_function_call = 22, + sym_argument_list = 23, + sym_positional_argument = 24, + sym_value = 25, + sym_boolean_literal = 26, + sym_quoted_literal = 27, + sym_double_quoted_literal = 28, + sym_backtick_quoted_literal = 29, + sym_temporal_literal = 30, + aux_sym_open_expression_repeat1 = 31, + aux_sym_argument_list_repeat1 = 32, + aux_sym_double_quoted_literal_repeat1 = 33, }; static const char * const ts_symbol_names[] = { [ts_builtin_sym_end] = "end", - [sym_expression] = "expression", + [anon_sym_PIPE] = "|", + [anon_sym_LPAREN] = "(", + [anon_sym_RPAREN] = ")", + [sym_function_name] = "function_name", + [anon_sym_COMMA] = ",", + [sym_numeric_literal] = "numeric_literal", + [anon_sym_POUNDtrue] = "#true", + [anon_sym_POUNDfalse] = "#false", + [anon_sym_DQUOTE] = "\"", + [sym_double_quoted_content] = "double_quoted_content", + [sym_escape_sequence] = "escape_sequence", + [anon_sym_BQUOTE] = "`", + [sym_backtick_quoted_content] = "backtick_quoted_content", + [sym_date_literal] = "date_literal", + [sym_date_time_literal] = "date_time_literal", + [sym_time_literal] = "time_literal", [sym_source_file] = "source_file", - [aux_sym_source_file_repeat1] = "source_file_repeat1", + [sym_root_expression] = "root_expression", + [sym_open_expression] = "open_expression", + [sym_closed_expression] = "closed_expression", + [sym_expression] = "expression", + [sym_function_call] = "function_call", + [sym_argument_list] = "argument_list", + [sym_positional_argument] = "positional_argument", + [sym_value] = "value", + [sym_boolean_literal] = "boolean_literal", + [sym_quoted_literal] = "quoted_literal", + [sym_double_quoted_literal] = "double_quoted_literal", + [sym_backtick_quoted_literal] = "backtick_quoted_literal", + [sym_temporal_literal] = "temporal_literal", + [aux_sym_open_expression_repeat1] = "open_expression_repeat1", + [aux_sym_argument_list_repeat1] = "argument_list_repeat1", + [aux_sym_double_quoted_literal_repeat1] = "double_quoted_literal_repeat1", }; static const TSSymbol ts_symbol_map[] = { [ts_builtin_sym_end] = ts_builtin_sym_end, - [sym_expression] = sym_expression, + [anon_sym_PIPE] = anon_sym_PIPE, + [anon_sym_LPAREN] = anon_sym_LPAREN, + [anon_sym_RPAREN] = anon_sym_RPAREN, + [sym_function_name] = sym_function_name, + [anon_sym_COMMA] = anon_sym_COMMA, + [sym_numeric_literal] = sym_numeric_literal, + [anon_sym_POUNDtrue] = anon_sym_POUNDtrue, + [anon_sym_POUNDfalse] = anon_sym_POUNDfalse, + [anon_sym_DQUOTE] = anon_sym_DQUOTE, + [sym_double_quoted_content] = sym_double_quoted_content, + [sym_escape_sequence] = sym_escape_sequence, + [anon_sym_BQUOTE] = anon_sym_BQUOTE, + [sym_backtick_quoted_content] = sym_backtick_quoted_content, + [sym_date_literal] = sym_date_literal, + [sym_date_time_literal] = sym_date_time_literal, + [sym_time_literal] = sym_time_literal, [sym_source_file] = sym_source_file, - [aux_sym_source_file_repeat1] = aux_sym_source_file_repeat1, + [sym_root_expression] = sym_root_expression, + [sym_open_expression] = sym_open_expression, + [sym_closed_expression] = sym_closed_expression, + [sym_expression] = sym_expression, + [sym_function_call] = sym_function_call, + [sym_argument_list] = sym_argument_list, + [sym_positional_argument] = sym_positional_argument, + [sym_value] = sym_value, + [sym_boolean_literal] = sym_boolean_literal, + [sym_quoted_literal] = sym_quoted_literal, + [sym_double_quoted_literal] = sym_double_quoted_literal, + [sym_backtick_quoted_literal] = sym_backtick_quoted_literal, + [sym_temporal_literal] = sym_temporal_literal, + [aux_sym_open_expression_repeat1] = aux_sym_open_expression_repeat1, + [aux_sym_argument_list_repeat1] = aux_sym_argument_list_repeat1, + [aux_sym_double_quoted_literal_repeat1] = aux_sym_double_quoted_literal_repeat1, }; static const TSSymbolMetadata ts_symbol_metadata[] = { @@ -44,7 +134,67 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .visible = false, .named = true, }, - [sym_expression] = { + [anon_sym_PIPE] = { + .visible = true, + .named = false, + }, + [anon_sym_LPAREN] = { + .visible = true, + .named = false, + }, + [anon_sym_RPAREN] = { + .visible = true, + .named = false, + }, + [sym_function_name] = { + .visible = true, + .named = true, + }, + [anon_sym_COMMA] = { + .visible = true, + .named = false, + }, + [sym_numeric_literal] = { + .visible = true, + .named = true, + }, + [anon_sym_POUNDtrue] = { + .visible = true, + .named = false, + }, + [anon_sym_POUNDfalse] = { + .visible = true, + .named = false, + }, + [anon_sym_DQUOTE] = { + .visible = true, + .named = false, + }, + [sym_double_quoted_content] = { + .visible = true, + .named = true, + }, + [sym_escape_sequence] = { + .visible = true, + .named = true, + }, + [anon_sym_BQUOTE] = { + .visible = true, + .named = false, + }, + [sym_backtick_quoted_content] = { + .visible = true, + .named = true, + }, + [sym_date_literal] = { + .visible = true, + .named = true, + }, + [sym_date_time_literal] = { + .visible = true, + .named = true, + }, + [sym_time_literal] = { .visible = true, .named = true, }, @@ -52,12 +202,90 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .visible = true, .named = true, }, - [aux_sym_source_file_repeat1] = { + [sym_root_expression] = { + .visible = true, + .named = true, + }, + [sym_open_expression] = { + .visible = true, + .named = true, + }, + [sym_closed_expression] = { + .visible = true, + .named = true, + }, + [sym_expression] = { + .visible = true, + .named = true, + }, + [sym_function_call] = { + .visible = true, + .named = true, + }, + [sym_argument_list] = { + .visible = true, + .named = true, + }, + [sym_positional_argument] = { + .visible = true, + .named = true, + }, + [sym_value] = { + .visible = true, + .named = true, + }, + [sym_boolean_literal] = { + .visible = true, + .named = true, + }, + [sym_quoted_literal] = { + .visible = true, + .named = true, + }, + [sym_double_quoted_literal] = { + .visible = true, + .named = true, + }, + [sym_backtick_quoted_literal] = { + .visible = true, + .named = true, + }, + [sym_temporal_literal] = { + .visible = true, + .named = true, + }, + [aux_sym_open_expression_repeat1] = { + .visible = false, + .named = false, + }, + [aux_sym_argument_list_repeat1] = { + .visible = false, + .named = false, + }, + [aux_sym_double_quoted_literal_repeat1] = { .visible = false, .named = false, }, }; +enum ts_field_identifiers { + field_name = 1, +}; + +static const char * const ts_field_names[] = { + [0] = NULL, + [field_name] = "name", +}; + +static const TSMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = { + [1] = {.index = 0, .length = 1}, +}; + +static const TSFieldMapEntry ts_field_map_entries[] = { + [0] = + {field_name, 0}, +}; + static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = { [0] = {0}, }; @@ -72,6 +300,40 @@ static const TSStateId ts_primary_state_ids[STATE_COUNT] = { [2] = 2, [3] = 3, [4] = 4, + [5] = 5, + [6] = 6, + [7] = 7, + [8] = 8, + [9] = 9, + [10] = 10, + [11] = 11, + [12] = 12, + [13] = 13, + [14] = 14, + [15] = 15, + [16] = 16, + [17] = 17, + [18] = 18, + [19] = 19, + [20] = 20, + [21] = 21, + [22] = 22, + [23] = 23, + [24] = 24, + [25] = 25, + [26] = 26, + [27] = 27, + [28] = 28, + [29] = 29, + [30] = 30, + [31] = 31, + [32] = 32, + [33] = 33, + [34] = 34, + [35] = 35, + [36] = 36, + [37] = 37, + [38] = 38, }; static bool ts_lex(TSLexer *lexer, TSStateId state) { @@ -79,28 +341,310 @@ static bool ts_lex(TSLexer *lexer, TSStateId state) { eof = lexer->eof(lexer); switch (state) { case 0: - if (eof) ADVANCE(5); - if (lookahead == 'h') ADVANCE(1); + if (eof) ADVANCE(44); + ADVANCE_MAP( + '"', 55, + '#', 3, + '(', 46, + ')', 47, + ',', 49, + '-', 9, + '0', 50, + '\\', 23, + '`', 59, + '|', 45, + ); if (('\t' <= lookahead && lookahead <= '\r') || - lookahead == ' ') SKIP(0); + lookahead == ' ' || + lookahead == 0x200b || + lookahead == 0x2060 || + lookahead == 0xfeff) SKIP(43); + if (('1' <= lookahead && lookahead <= '9')) ADVANCE(51); + if (('A' <= lookahead && lookahead <= 'Z') || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(48); END_STATE(); case 1: - if (lookahead == 'e') ADVANCE(3); + if (lookahead == '"') ADVANCE(55); + if (lookahead == '\\') ADVANCE(23); + if (lookahead == '\n' || + lookahead == '\r') SKIP(2); + if (('\t' <= lookahead && lookahead <= '\f') || + lookahead == ' ' || + lookahead == 0x200b || + lookahead == 0x2060 || + lookahead == 0xfeff) ADVANCE(56); + if (lookahead != 0) ADVANCE(57); END_STATE(); case 2: - if (lookahead == 'l') ADVANCE(4); + if (lookahead == '"') ADVANCE(55); + if (('\t' <= lookahead && lookahead <= '\r') || + lookahead == ' ' || + lookahead == 0x200b || + lookahead == 0x2060 || + lookahead == 0xfeff) SKIP(2); END_STATE(); case 3: - if (lookahead == 'l') ADVANCE(2); + if (lookahead == '"') ADVANCE(31); + if (lookahead == 'f') ADVANCE(16); + if (lookahead == 't') ADVANCE(20); END_STATE(); case 4: - if (lookahead == 'o') ADVANCE(6); + if (lookahead == '"') ADVANCE(64); END_STATE(); case 5: - ACCEPT_TOKEN(ts_builtin_sym_end); + if (lookahead == '"') ADVANCE(62); + if (lookahead == ' ' || + lookahead == 'T') ADVANCE(39); END_STATE(); case 6: - ACCEPT_TOKEN(sym_expression); + if (lookahead == '"') ADVANCE(63); + END_STATE(); + case 7: + if (lookahead == '-') ADVANCE(38); + END_STATE(); + case 8: + if (lookahead == '-') ADVANCE(34); + END_STATE(); + case 9: + if (lookahead == '0') ADVANCE(50); + if (('1' <= lookahead && lookahead <= '9')) ADVANCE(51); + END_STATE(); + case 10: + if (lookahead == ':') ADVANCE(32); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(26); + END_STATE(); + case 11: + if (lookahead == ':') ADVANCE(33); + END_STATE(); + case 12: + if (lookahead == ':') ADVANCE(35); + END_STATE(); + case 13: + if (lookahead == ':') ADVANCE(41); + END_STATE(); + case 14: + if (lookahead == '`') ADVANCE(59); + if (lookahead == '\n' || + lookahead == '\r') SKIP(15); + if (('\t' <= lookahead && lookahead <= '\f') || + lookahead == ' ' || + lookahead == 0x200b || + lookahead == 0x2060 || + lookahead == 0xfeff) ADVANCE(60); + if (lookahead != 0) ADVANCE(61); + END_STATE(); + case 15: + if (lookahead == '`') ADVANCE(59); + if (('\t' <= lookahead && lookahead <= '\r') || + lookahead == ' ' || + lookahead == 0x200b || + lookahead == 0x2060 || + lookahead == 0xfeff) SKIP(15); + END_STATE(); + case 16: + if (lookahead == 'a') ADVANCE(19); + END_STATE(); + case 17: + if (lookahead == 'e') ADVANCE(53); + END_STATE(); + case 18: + if (lookahead == 'e') ADVANCE(54); + END_STATE(); + case 19: + if (lookahead == 'l') ADVANCE(21); + END_STATE(); + case 20: + if (lookahead == 'r') ADVANCE(22); + END_STATE(); + case 21: + if (lookahead == 's') ADVANCE(18); + END_STATE(); + case 22: + if (lookahead == 'u') ADVANCE(17); + END_STATE(); + case 23: + if (lookahead == '"' || + lookahead == '\\') ADVANCE(58); + END_STATE(); + case 24: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(52); + END_STATE(); + case 25: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(10); + END_STATE(); + case 26: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(7); + END_STATE(); + case 27: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(11); + END_STATE(); + case 28: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(4); + END_STATE(); + case 29: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(5); + END_STATE(); + case 30: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(6); + END_STATE(); + case 31: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(25); + END_STATE(); + case 32: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(27); + END_STATE(); + case 33: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(28); + END_STATE(); + case 34: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(29); + END_STATE(); + case 35: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(30); + END_STATE(); + case 36: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(8); + END_STATE(); + case 37: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(13); + END_STATE(); + case 38: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(36); + END_STATE(); + case 39: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(37); + END_STATE(); + case 40: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(12); + END_STATE(); + case 41: + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(40); + END_STATE(); + case 42: + if (('A' <= lookahead && lookahead <= 'Z') || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(48); + END_STATE(); + case 43: + if (eof) ADVANCE(44); + ADVANCE_MAP( + '"', 55, + '#', 3, + '(', 46, + ')', 47, + ',', 49, + '-', 9, + '0', 50, + '`', 59, + '|', 45, + ); + if (('\t' <= lookahead && lookahead <= '\r') || + lookahead == ' ' || + lookahead == 0x200b || + lookahead == 0x2060 || + lookahead == 0xfeff) SKIP(43); + if (('1' <= lookahead && lookahead <= '9')) ADVANCE(51); + if (('A' <= lookahead && lookahead <= 'Z') || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(48); + END_STATE(); + case 44: + ACCEPT_TOKEN(ts_builtin_sym_end); + END_STATE(); + case 45: + ACCEPT_TOKEN(anon_sym_PIPE); + END_STATE(); + case 46: + ACCEPT_TOKEN(anon_sym_LPAREN); + END_STATE(); + case 47: + ACCEPT_TOKEN(anon_sym_RPAREN); + END_STATE(); + case 48: + ACCEPT_TOKEN(sym_function_name); + if (lookahead == '-') ADVANCE(42); + if (('A' <= lookahead && lookahead <= 'Z') || + ('a' <= lookahead && lookahead <= 'z')) ADVANCE(48); + END_STATE(); + case 49: + ACCEPT_TOKEN(anon_sym_COMMA); + END_STATE(); + case 50: + ACCEPT_TOKEN(sym_numeric_literal); + if (lookahead == '.') ADVANCE(24); + END_STATE(); + case 51: + ACCEPT_TOKEN(sym_numeric_literal); + if (lookahead == '.') ADVANCE(24); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(51); + END_STATE(); + case 52: + ACCEPT_TOKEN(sym_numeric_literal); + if (('0' <= lookahead && lookahead <= '9')) ADVANCE(52); + END_STATE(); + case 53: + ACCEPT_TOKEN(anon_sym_POUNDtrue); + END_STATE(); + case 54: + ACCEPT_TOKEN(anon_sym_POUNDfalse); + END_STATE(); + case 55: + ACCEPT_TOKEN(anon_sym_DQUOTE); + END_STATE(); + case 56: + ACCEPT_TOKEN(sym_double_quoted_content); + if (lookahead == '\t' || + lookahead == 0x0b || + lookahead == '\f' || + lookahead == ' ' || + lookahead == 0x200b || + lookahead == 0x2060 || + lookahead == 0xfeff) ADVANCE(56); + if (lookahead != 0 && + (lookahead < '\t' || '\r' < lookahead) && + lookahead != '"' && + lookahead != '\\') ADVANCE(57); + END_STATE(); + case 57: + ACCEPT_TOKEN(sym_double_quoted_content); + if (lookahead != 0 && + lookahead != '\n' && + lookahead != '\r' && + lookahead != '"' && + lookahead != '\\') ADVANCE(57); + END_STATE(); + case 58: + ACCEPT_TOKEN(sym_escape_sequence); + END_STATE(); + case 59: + ACCEPT_TOKEN(anon_sym_BQUOTE); + END_STATE(); + case 60: + ACCEPT_TOKEN(sym_backtick_quoted_content); + if (lookahead == '\t' || + lookahead == 0x0b || + lookahead == '\f' || + lookahead == ' ' || + lookahead == 0x200b || + lookahead == 0x2060 || + lookahead == 0xfeff) ADVANCE(60); + if (lookahead != 0 && + (lookahead < '\t' || '\r' < lookahead) && + lookahead != '`') ADVANCE(61); + END_STATE(); + case 61: + ACCEPT_TOKEN(sym_backtick_quoted_content); + if (lookahead != 0 && + lookahead != '\n' && + lookahead != '\r' && + lookahead != '`') ADVANCE(61); + END_STATE(); + case 62: + ACCEPT_TOKEN(sym_date_literal); + END_STATE(); + case 63: + ACCEPT_TOKEN(sym_date_time_literal); + END_STATE(); + case 64: + ACCEPT_TOKEN(sym_time_literal); END_STATE(); default: return false; @@ -112,52 +656,431 @@ static const TSLexerMode ts_lex_modes[STATE_COUNT] = { [1] = {.lex_state = 0}, [2] = {.lex_state = 0}, [3] = {.lex_state = 0}, - [4] = {.lex_state = 0}, + [4] = {.lex_state = 1}, + [5] = {.lex_state = 0}, + [6] = {.lex_state = 0}, + [7] = {.lex_state = 0}, + [8] = {.lex_state = 0}, + [9] = {.lex_state = 1}, + [10] = {.lex_state = 0}, + [11] = {.lex_state = 1}, + [12] = {.lex_state = 0}, + [13] = {.lex_state = 0}, + [14] = {.lex_state = 0}, + [15] = {.lex_state = 0}, + [16] = {.lex_state = 0}, + [17] = {.lex_state = 0}, + [18] = {.lex_state = 0}, + [19] = {.lex_state = 0}, + [20] = {.lex_state = 0}, + [21] = {.lex_state = 0}, + [22] = {.lex_state = 0}, + [23] = {.lex_state = 0}, + [24] = {.lex_state = 0}, + [25] = {.lex_state = 0}, + [26] = {.lex_state = 14}, + [27] = {.lex_state = 0}, + [28] = {.lex_state = 0}, + [29] = {.lex_state = 0}, + [30] = {.lex_state = 0}, + [31] = {.lex_state = 0}, + [32] = {.lex_state = 0}, + [33] = {.lex_state = 0}, + [34] = {.lex_state = 0}, + [35] = {.lex_state = 0}, + [36] = {.lex_state = 0}, + [37] = {.lex_state = 0}, + [38] = {.lex_state = 0}, }; static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { [STATE(0)] = { [ts_builtin_sym_end] = ACTIONS(1), - [sym_expression] = ACTIONS(1), + [anon_sym_PIPE] = ACTIONS(1), + [anon_sym_LPAREN] = ACTIONS(1), + [anon_sym_RPAREN] = ACTIONS(1), + [sym_function_name] = ACTIONS(1), + [anon_sym_COMMA] = ACTIONS(1), + [sym_numeric_literal] = ACTIONS(1), + [anon_sym_POUNDtrue] = ACTIONS(1), + [anon_sym_POUNDfalse] = ACTIONS(1), + [anon_sym_DQUOTE] = ACTIONS(1), + [sym_escape_sequence] = ACTIONS(1), + [anon_sym_BQUOTE] = ACTIONS(1), + [sym_date_literal] = ACTIONS(1), + [sym_date_time_literal] = ACTIONS(1), + [sym_time_literal] = ACTIONS(1), }, [STATE(1)] = { - [sym_source_file] = STATE(4), - [aux_sym_source_file_repeat1] = STATE(2), - [ts_builtin_sym_end] = ACTIONS(3), - [sym_expression] = ACTIONS(5), - }, - [STATE(2)] = { - [aux_sym_source_file_repeat1] = STATE(3), - [ts_builtin_sym_end] = ACTIONS(7), - [sym_expression] = ACTIONS(9), - }, - [STATE(3)] = { - [aux_sym_source_file_repeat1] = STATE(3), - [ts_builtin_sym_end] = ACTIONS(11), - [sym_expression] = ACTIONS(13), + [sym_source_file] = STATE(37), + [sym_root_expression] = STATE(38), + [sym_open_expression] = STATE(35), + [sym_closed_expression] = STATE(35), + [sym_expression] = STATE(16), + [sym_function_call] = STATE(28), + [sym_value] = STATE(30), + [sym_boolean_literal] = STATE(13), + [sym_quoted_literal] = STATE(13), + [sym_double_quoted_literal] = STATE(7), + [sym_backtick_quoted_literal] = STATE(7), + [sym_temporal_literal] = STATE(13), + [sym_function_name] = ACTIONS(3), + [sym_numeric_literal] = ACTIONS(5), + [anon_sym_POUNDtrue] = ACTIONS(7), + [anon_sym_POUNDfalse] = ACTIONS(7), + [anon_sym_DQUOTE] = ACTIONS(9), + [anon_sym_BQUOTE] = ACTIONS(11), + [sym_date_literal] = ACTIONS(13), + [sym_date_time_literal] = ACTIONS(13), + [sym_time_literal] = ACTIONS(13), }, }; static const uint16_t ts_small_parse_table[] = { - [0] = 1, - ACTIONS(16), 1, + [0] = 11, + ACTIONS(5), 1, + sym_numeric_literal, + ACTIONS(9), 1, + anon_sym_DQUOTE, + ACTIONS(11), 1, + anon_sym_BQUOTE, + ACTIONS(15), 1, + anon_sym_RPAREN, + STATE(19), 1, + sym_positional_argument, + STATE(29), 1, + sym_value, + STATE(36), 1, + sym_argument_list, + ACTIONS(7), 2, + anon_sym_POUNDtrue, + anon_sym_POUNDfalse, + STATE(7), 2, + sym_double_quoted_literal, + sym_backtick_quoted_literal, + ACTIONS(13), 3, + sym_date_literal, + sym_date_time_literal, + sym_time_literal, + STATE(13), 3, + sym_boolean_literal, + sym_quoted_literal, + sym_temporal_literal, + [40] = 9, + ACTIONS(5), 1, + sym_numeric_literal, + ACTIONS(9), 1, + anon_sym_DQUOTE, + ACTIONS(11), 1, + anon_sym_BQUOTE, + STATE(29), 1, + sym_value, + STATE(33), 1, + sym_positional_argument, + ACTIONS(7), 2, + anon_sym_POUNDtrue, + anon_sym_POUNDfalse, + STATE(7), 2, + sym_double_quoted_literal, + sym_backtick_quoted_literal, + ACTIONS(13), 3, + sym_date_literal, + sym_date_time_literal, + sym_time_literal, + STATE(13), 3, + sym_boolean_literal, + sym_quoted_literal, + sym_temporal_literal, + [74] = 3, + ACTIONS(17), 1, + anon_sym_DQUOTE, + STATE(4), 1, + aux_sym_double_quoted_literal_repeat1, + ACTIONS(19), 2, + sym_double_quoted_content, + sym_escape_sequence, + [85] = 1, + ACTIONS(22), 4, + ts_builtin_sym_end, + anon_sym_PIPE, + anon_sym_RPAREN, + anon_sym_COMMA, + [92] = 1, + ACTIONS(24), 4, + ts_builtin_sym_end, + anon_sym_PIPE, + anon_sym_RPAREN, + anon_sym_COMMA, + [99] = 1, + ACTIONS(26), 4, + ts_builtin_sym_end, + anon_sym_PIPE, + anon_sym_RPAREN, + anon_sym_COMMA, + [106] = 1, + ACTIONS(28), 4, + ts_builtin_sym_end, + anon_sym_PIPE, + anon_sym_RPAREN, + anon_sym_COMMA, + [113] = 3, + ACTIONS(30), 1, + anon_sym_DQUOTE, + STATE(4), 1, + aux_sym_double_quoted_literal_repeat1, + ACTIONS(32), 2, + sym_double_quoted_content, + sym_escape_sequence, + [124] = 1, + ACTIONS(34), 4, + ts_builtin_sym_end, + anon_sym_PIPE, + anon_sym_RPAREN, + anon_sym_COMMA, + [131] = 3, + ACTIONS(36), 1, + anon_sym_DQUOTE, + STATE(9), 1, + aux_sym_double_quoted_literal_repeat1, + ACTIONS(38), 2, + sym_double_quoted_content, + sym_escape_sequence, + [142] = 1, + ACTIONS(40), 4, + ts_builtin_sym_end, + anon_sym_PIPE, + anon_sym_RPAREN, + anon_sym_COMMA, + [149] = 1, + ACTIONS(42), 4, + ts_builtin_sym_end, + anon_sym_PIPE, + anon_sym_RPAREN, + anon_sym_COMMA, + [156] = 1, + ACTIONS(44), 4, + ts_builtin_sym_end, + anon_sym_PIPE, + anon_sym_RPAREN, + anon_sym_COMMA, + [163] = 3, + ACTIONS(46), 1, + ts_builtin_sym_end, + ACTIONS(48), 1, + anon_sym_PIPE, + STATE(22), 1, + aux_sym_open_expression_repeat1, + [173] = 3, + ACTIONS(48), 1, + anon_sym_PIPE, + ACTIONS(50), 1, + ts_builtin_sym_end, + STATE(15), 1, + aux_sym_open_expression_repeat1, + [183] = 3, + ACTIONS(3), 1, + sym_function_name, + STATE(28), 1, + sym_function_call, + STATE(32), 1, + sym_expression, + [193] = 3, + ACTIONS(3), 1, + sym_function_name, + STATE(23), 1, + sym_expression, + STATE(28), 1, + sym_function_call, + [203] = 3, + ACTIONS(52), 1, + anon_sym_RPAREN, + ACTIONS(54), 1, + anon_sym_COMMA, + STATE(21), 1, + aux_sym_argument_list_repeat1, + [213] = 2, + ACTIONS(58), 1, + anon_sym_LPAREN, + ACTIONS(56), 2, + ts_builtin_sym_end, + anon_sym_PIPE, + [221] = 3, + ACTIONS(54), 1, + anon_sym_COMMA, + ACTIONS(60), 1, + anon_sym_RPAREN, + STATE(25), 1, + aux_sym_argument_list_repeat1, + [231] = 3, + ACTIONS(62), 1, + ts_builtin_sym_end, + ACTIONS(64), 1, + anon_sym_PIPE, + STATE(22), 1, + aux_sym_open_expression_repeat1, + [241] = 3, + ACTIONS(48), 1, + anon_sym_PIPE, + ACTIONS(67), 1, + ts_builtin_sym_end, + STATE(24), 1, + aux_sym_open_expression_repeat1, + [251] = 3, + ACTIONS(48), 1, + anon_sym_PIPE, + ACTIONS(69), 1, + ts_builtin_sym_end, + STATE(22), 1, + aux_sym_open_expression_repeat1, + [261] = 3, + ACTIONS(71), 1, + anon_sym_RPAREN, + ACTIONS(73), 1, + anon_sym_COMMA, + STATE(25), 1, + aux_sym_argument_list_repeat1, + [271] = 2, + ACTIONS(76), 1, + anon_sym_BQUOTE, + ACTIONS(78), 1, + sym_backtick_quoted_content, + [278] = 1, + ACTIONS(80), 2, + ts_builtin_sym_end, + anon_sym_PIPE, + [283] = 1, + ACTIONS(82), 2, + ts_builtin_sym_end, + anon_sym_PIPE, + [288] = 1, + ACTIONS(84), 2, + anon_sym_RPAREN, + anon_sym_COMMA, + [293] = 2, + ACTIONS(86), 1, + ts_builtin_sym_end, + ACTIONS(88), 1, + anon_sym_PIPE, + [300] = 1, + ACTIONS(90), 2, + ts_builtin_sym_end, + anon_sym_PIPE, + [305] = 1, + ACTIONS(62), 2, + ts_builtin_sym_end, + anon_sym_PIPE, + [310] = 1, + ACTIONS(71), 2, + anon_sym_RPAREN, + anon_sym_COMMA, + [315] = 1, + ACTIONS(92), 1, + anon_sym_BQUOTE, + [319] = 1, + ACTIONS(94), 1, + ts_builtin_sym_end, + [323] = 1, + ACTIONS(96), 1, + anon_sym_RPAREN, + [327] = 1, + ACTIONS(98), 1, + ts_builtin_sym_end, + [331] = 1, + ACTIONS(100), 1, ts_builtin_sym_end, }; static const uint32_t ts_small_parse_table_map[] = { - [SMALL_STATE(4)] = 0, + [SMALL_STATE(2)] = 0, + [SMALL_STATE(3)] = 40, + [SMALL_STATE(4)] = 74, + [SMALL_STATE(5)] = 85, + [SMALL_STATE(6)] = 92, + [SMALL_STATE(7)] = 99, + [SMALL_STATE(8)] = 106, + [SMALL_STATE(9)] = 113, + [SMALL_STATE(10)] = 124, + [SMALL_STATE(11)] = 131, + [SMALL_STATE(12)] = 142, + [SMALL_STATE(13)] = 149, + [SMALL_STATE(14)] = 156, + [SMALL_STATE(15)] = 163, + [SMALL_STATE(16)] = 173, + [SMALL_STATE(17)] = 183, + [SMALL_STATE(18)] = 193, + [SMALL_STATE(19)] = 203, + [SMALL_STATE(20)] = 213, + [SMALL_STATE(21)] = 221, + [SMALL_STATE(22)] = 231, + [SMALL_STATE(23)] = 241, + [SMALL_STATE(24)] = 251, + [SMALL_STATE(25)] = 261, + [SMALL_STATE(26)] = 271, + [SMALL_STATE(27)] = 278, + [SMALL_STATE(28)] = 283, + [SMALL_STATE(29)] = 288, + [SMALL_STATE(30)] = 293, + [SMALL_STATE(31)] = 300, + [SMALL_STATE(32)] = 305, + [SMALL_STATE(33)] = 310, + [SMALL_STATE(34)] = 315, + [SMALL_STATE(35)] = 319, + [SMALL_STATE(36)] = 323, + [SMALL_STATE(37)] = 327, + [SMALL_STATE(38)] = 331, }; static const TSParseActionEntry ts_parse_actions[] = { [0] = {.entry = {.count = 0, .reusable = false}}, [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(), - [3] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 0, 0, 0), - [5] = {.entry = {.count = 1, .reusable = true}}, SHIFT(2), - [7] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 1, 0, 0), - [9] = {.entry = {.count = 1, .reusable = true}}, SHIFT(3), - [11] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), - [13] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_source_file_repeat1, 2, 0, 0), SHIFT_REPEAT(3), - [16] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(), + [3] = {.entry = {.count = 1, .reusable = true}}, SHIFT(20), + [5] = {.entry = {.count = 1, .reusable = true}}, SHIFT(13), + [7] = {.entry = {.count = 1, .reusable = true}}, SHIFT(5), + [9] = {.entry = {.count = 1, .reusable = true}}, SHIFT(11), + [11] = {.entry = {.count = 1, .reusable = true}}, SHIFT(26), + [13] = {.entry = {.count = 1, .reusable = true}}, SHIFT(6), + [15] = {.entry = {.count = 1, .reusable = true}}, SHIFT(27), + [17] = {.entry = {.count = 1, .reusable = false}}, REDUCE(aux_sym_double_quoted_literal_repeat1, 2, 0, 0), + [19] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_double_quoted_literal_repeat1, 2, 0, 0), SHIFT_REPEAT(4), + [22] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_boolean_literal, 1, 0, 0), + [24] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_temporal_literal, 1, 0, 0), + [26] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_quoted_literal, 1, 0, 0), + [28] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_double_quoted_literal, 2, 0, 0), + [30] = {.entry = {.count = 1, .reusable = false}}, SHIFT(12), + [32] = {.entry = {.count = 1, .reusable = true}}, SHIFT(4), + [34] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_backtick_quoted_literal, 2, 0, 0), + [36] = {.entry = {.count = 1, .reusable = false}}, SHIFT(8), + [38] = {.entry = {.count = 1, .reusable = true}}, SHIFT(9), + [40] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_double_quoted_literal, 3, 0, 0), + [42] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_value, 1, 0, 0), + [44] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_backtick_quoted_literal, 3, 0, 0), + [46] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_open_expression, 2, 0, 0), + [48] = {.entry = {.count = 1, .reusable = true}}, SHIFT(17), + [50] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_open_expression, 1, 0, 0), + [52] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_argument_list, 1, 0, 0), + [54] = {.entry = {.count = 1, .reusable = true}}, SHIFT(3), + [56] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_call, 1, 0, 1), + [58] = {.entry = {.count = 1, .reusable = true}}, SHIFT(2), + [60] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_argument_list, 2, 0, 0), + [62] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_open_expression_repeat1, 2, 0, 0), + [64] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_open_expression_repeat1, 2, 0, 0), SHIFT_REPEAT(17), + [67] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_closed_expression, 3, 0, 0), + [69] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_closed_expression, 4, 0, 0), + [71] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_argument_list_repeat1, 2, 0, 0), + [73] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_argument_list_repeat1, 2, 0, 0), SHIFT_REPEAT(3), + [76] = {.entry = {.count = 1, .reusable = false}}, SHIFT(10), + [78] = {.entry = {.count = 1, .reusable = true}}, SHIFT(34), + [80] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_call, 3, 0, 1), + [82] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_expression, 1, 0, 0), + [84] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_positional_argument, 1, 0, 0), + [86] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_closed_expression, 1, 0, 0), + [88] = {.entry = {.count = 1, .reusable = true}}, SHIFT(18), + [90] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_function_call, 4, 0, 1), + [92] = {.entry = {.count = 1, .reusable = true}}, SHIFT(14), + [94] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_root_expression, 1, 0, 0), + [96] = {.entry = {.count = 1, .reusable = true}}, SHIFT(31), + [98] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(), + [100] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source_file, 1, 0, 0), }; #ifdef __cplusplus @@ -189,6 +1112,9 @@ TS_PUBLIC const TSLanguage *tree_sitter_expressif(void) { .small_parse_table_map = ts_small_parse_table_map, .parse_actions = ts_parse_actions, .symbol_names = ts_symbol_names, + .field_names = ts_field_names, + .field_map_slices = ts_field_map_slices, + .field_map_entries = ts_field_map_entries, .symbol_metadata = ts_symbol_metadata, .public_symbol_map = ts_symbol_map, .alias_map = ts_non_terminal_alias_map, diff --git a/test/corpus/expressions.txt b/test/corpus/expressions.txt index 94c6fda..143bc5f 100644 --- a/test/corpus/expressions.txt +++ b/test/corpus/expressions.txt @@ -1,10 +1,309 @@ ================== -Placeholder expression +Bare function call ================== -hello +lower --- (source_file - (expression)) + (root_expression + (open_expression + (expression + (function_call + name: (function_name)))))) + +================== +Empty parenthesized function call +================== + +lower() + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + name: (function_name)))))) + +================== +Mixed-case hyphenated function name +================== + +foo-BAr-foo + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + name: (function_name)))))) + +================== +Function call with positional arguments +================== + +example(10, 20) + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + name: (function_name) + (argument_list + (positional_argument + (value + (numeric_literal))) + (positional_argument + (value + (numeric_literal))))))))) + +================== +Open expression pipeline +================== + +remove-chars("a") | upper | trim() + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + name: (function_name) + (argument_list + (positional_argument + (value + (quoted_literal + (double_quoted_literal + (double_quoted_content)))))))) + (expression + (function_call + name: (function_name))) + (expression + (function_call + name: (function_name)))))) + +================== +Numeric literals and pipeline +================== + +-3.14 | round(2) + +--- + +(source_file + (root_expression + (closed_expression + (value + (numeric_literal)) + (expression + (function_call + name: (function_name) + (argument_list + (positional_argument + (value + (numeric_literal))))))))) + +================== +Boolean literals +================== + +#true | some-function(#false) + +--- + +(source_file + (root_expression + (closed_expression + (value + (boolean_literal)) + (expression + (function_call + name: (function_name) + (argument_list + (positional_argument + (value + (boolean_literal))))))))) + +================== +Bare boolean words are function calls +================== + +true | false() + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + name: (function_name))) + (expression + (function_call + name: (function_name)))))) + +================== +Double-quoted text with escape +================== + +"Alice said \"hello\"." + +--- + +(source_file + (root_expression + (closed_expression + (value + (quoted_literal + (double_quoted_literal + (double_quoted_content) + (escape_sequence) + (double_quoted_content) + (escape_sequence) + (double_quoted_content))))))) + +================== +Empty double-quoted text +================== + +"" + +--- + +(source_file + (root_expression + (closed_expression + (value + (quoted_literal + (double_quoted_literal)))))) + +================== +Backtick text preserves punctuation and whitespace +================== + +` foo, (bar) ` + +--- + +(source_file + (root_expression + (closed_expression + (value + (quoted_literal + (backtick_quoted_literal + (backtick_quoted_content))))))) + +================== +Date literal +================== + +#"2025-12-17" + +--- + +(source_file + (root_expression + (closed_expression + (value + (temporal_literal + (date_literal)))))) + +================== +Datetime literal with T separator +================== + +#"2025-12-17T14:30:00" + +--- + +(source_file + (root_expression + (closed_expression + (value + (temporal_literal + (date_time_literal)))))) + +================== +Datetime literal with space separator +================== + +#"2025-12-17 14:30:00" + +--- + +(source_file + (root_expression + (closed_expression + (value + (temporal_literal + (date_time_literal)))))) + +================== +Time literal +================== + +#"14:30:00" + +--- + +(source_file + (root_expression + (closed_expression + (value + (temporal_literal + (time_literal)))))) + +================== +Temporal-looking quoted text remains text +================== + +"2025-12-17" + +--- + +(source_file + (root_expression + (closed_expression + (value + (quoted_literal + (double_quoted_literal + (double_quoted_content))))))) + +================== +Whitespace is insignificant outside literals +================== + + add( 5 , 10 ) | multiply( 2 ) + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + name: (function_name) + (argument_list + (positional_argument + (value + (numeric_literal))) + (positional_argument + (value + (numeric_literal)))))) + (expression + (function_call + name: (function_name) + (argument_list + (positional_argument + (value + (numeric_literal))))))))) diff --git a/test/corpus/invalid.txt b/test/corpus/invalid.txt new file mode 100644 index 0000000..c8d278f --- /dev/null +++ b/test/corpus/invalid.txt @@ -0,0 +1,250 @@ +================== +Function name cannot start with a digit +================== + +1foo + +--- + +(source_file + (ERROR + (value + (numeric_literal))) + (root_expression + (open_expression + (expression + (function_call + (function_name)))))) + +================== +Function name cannot contain a digit +================== + +foo1 + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + (function_name))))) + (ERROR + (numeric_literal))) + +================== +Function name cannot start with a hyphen +================== + +-foo + +--- + +(source_file + (ERROR + (UNEXPECTED 'f')) + (root_expression + (open_expression + (expression + (function_call + (function_name)))))) + +================== +Function name cannot end with a hyphen +================== + +foo- + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + (function_name))))) + (ERROR + (UNEXPECTED '\n'))) + +================== +Function name cannot contain consecutive hyphens +================== + +foo--bar + +--- + +(source_file + (ERROR + (function_name) + (UNEXPECTED '-')) + (root_expression + (open_expression + (expression + (function_call + (function_name)))))) + +================== +Variables are not implemented +================== + +@foo + +--- + +(source_file + (ERROR + (UNEXPECTED '@')) + (root_expression + (open_expression + (expression + (function_call + (function_name)))))) + +================== +Unclosed function call +================== + +foo(5 + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + (function_name) + (argument_list + (positional_argument + (value + (numeric_literal)))) + (MISSING ")")))))) + +================== +Missing first argument +================== + +foo(, 5) + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + (function_name) + (ERROR) + (argument_list + (positional_argument + (value + (numeric_literal))))))))) + +================== +Trailing argument separator +================== + +foo(5,) + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + (function_name) + (argument_list + (positional_argument + (value + (numeric_literal)))) + (ERROR)))))) + +================== +Unquoted textual argument is rejected +================== + +remove-chars(a) + +--- + +(source_file + (root_expression + (open_expression + (expression + (function_call + (function_name) + (ERROR + (function_name))))))) + +================== +Malformed date literal +================== + +#"2025-2-17" + +--- + +(source_file + (ERROR + (UNEXPECTED '-')) + (root_expression + (closed_expression + (value + (numeric_literal)))) + (ERROR)) + +================== +Malformed datetime literal +================== + +#"2025-12-17T14:30" + +--- + +(source_file + (ERROR + (UNEXPECTED '"')) + (root_expression + (closed_expression + (value + (quoted_literal + (double_quoted_literal + (MISSING """))))))) + +================== +Malformed time literal +================== + +#"14:30" + +--- + +(source_file + (ERROR + (UNEXPECTED '"')) + (root_expression + (closed_expression + (value + (quoted_literal + (double_quoted_literal + (MISSING """))))))) + +================== +Boolean literal is case-sensitive +================== + +#True + +--- + +(source_file + (ERROR + (UNEXPECTED 'T')) + (root_expression + (open_expression + (expression + (function_call + (function_name)))))) From d45a64e8512d2ff0cee301d2989fbd74090d4695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20L=2E=20Charlier?= Date: Sun, 9 Aug 2026 09:36:56 +0200 Subject: [PATCH 4/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa25b4f..435fdd1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Expressif.Syntax -Expressif.Syntax provides the [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) parser for the [Expressif](https://github.com/Seddryck/Expressif) expression language, together with bindings for supported programming languages. +`Expressif.Syntax` provides the [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) parser for the [Expressif](https://github.com/Seddryck/Expressif) expression language, together with bindings for supported programming languages. The parser defines the concrete syntax of Expressif independently from its runtime implementations. It is intended to provide a common syntax foundation for the C#, Python and TypeScript implementations of Expressif, as well as editor tooling and language-server support. From 57e87c4a558d927e6ebb466ec211b1f81cc0839f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20L=2E=20Charlier?= Date: Sun, 9 Aug 2026 10:01:57 +0200 Subject: [PATCH 5/6] Update SECURITY.md --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index 92e3419..5a4f2d4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Reporting a Vulnerability -If you've found a security issue in Expressif-Parser, you can submit your report with [GitHub's private vulnerabilty reporting feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) on the corresponding repository. +If you've found a security issue in Expressif-Parser, you can submit your report with [GitHub's private vulnerability reporting feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) on the corresponding repository. Please include as much information as possible in your report to better help us understand and resolve the issue: From d2d1977a36bb35e1aae4dcb844bc90eb30f64fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20L=2E=20Charlier?= Date: Sun, 9 Aug 2026 10:04:41 +0200 Subject: [PATCH 6/6] fix: address parser review feedback --- .github/workflows/ci.yml | 32 ++++- CMakeLists.txt | 1 + Makefile | 2 +- bindings/node/index.d.ts | 7 +- grammar.js | 9 ++ package.json | 2 +- pyproject.toml | 2 +- queries/highlights.scm | 11 ++ src/grammar.json | 7 +- src/node-types.json | 148 ++++++++++------------- src/parser.c | 48 +++++++- test/corpus/expressions.txt | 232 +++++++++++++++++++----------------- test/corpus/invalid.txt | 222 ++++++++++++++++++++++++---------- 13 files changed, 451 insertions(+), 272 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c89336..869f225 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,8 @@ jobs: steps: - name: Check out repository uses: actions/checkout@v6 + with: + persist-credentials: false - name: Set up Tree-sitter uses: tree-sitter/setup-action/cli@v2 @@ -36,6 +38,8 @@ jobs: steps: - name: Check out repository uses: actions/checkout@v6 + with: + persist-credentials: false - name: Set up .NET uses: actions/setup-dotnet@v5 @@ -46,7 +50,12 @@ jobs: id: csharp-projects shell: pwsh run: | - $projects = @(Get-ChildItem bindings/csharp -Recurse -Filter *.csproj) + $projectRoot = "bindings/csharp" + $projects = if (Test-Path -LiteralPath $projectRoot -PathType Container) { + @(Get-ChildItem -LiteralPath $projectRoot -Recurse -Filter *.csproj) + } else { + @() + } "found=$($projects.Count -gt 0)" >> $env:GITHUB_OUTPUT if ($projects.Count -eq 0) { Write-Host "No C# projects exist yet; skipping .NET validation." @@ -56,7 +65,12 @@ jobs: if: steps.csharp-projects.outputs.found == 'True' shell: pwsh run: | - Get-ChildItem bindings/csharp -Recurse -Filter *.csproj | ForEach-Object { + $projectRoot = "bindings/csharp" + if (-not (Test-Path -LiteralPath $projectRoot -PathType Container)) { + Write-Host "No C# project directory exists; skipping restore." + exit 0 + } + Get-ChildItem -LiteralPath $projectRoot -Recurse -Filter *.csproj | ForEach-Object { dotnet restore $_.FullName if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } @@ -65,7 +79,12 @@ jobs: if: steps.csharp-projects.outputs.found == 'True' shell: pwsh run: | - Get-ChildItem bindings/csharp -Recurse -Filter *.csproj | ForEach-Object { + $projectRoot = "bindings/csharp" + if (-not (Test-Path -LiteralPath $projectRoot -PathType Container)) { + Write-Host "No C# project directory exists; skipping build." + exit 0 + } + Get-ChildItem -LiteralPath $projectRoot -Recurse -Filter *.csproj | ForEach-Object { dotnet build $_.FullName --configuration Release --no-restore if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } @@ -74,7 +93,12 @@ jobs: if: steps.csharp-projects.outputs.found == 'True' shell: pwsh run: | - Get-ChildItem bindings/csharp -Recurse -Filter *.csproj | ForEach-Object { + $projectRoot = "bindings/csharp" + if (-not (Test-Path -LiteralPath $projectRoot -PathType Container)) { + Write-Host "No C# project directory exists; skipping tests." + exit 0 + } + Get-ChildItem -LiteralPath $projectRoot -Recurse -Filter *.csproj | ForEach-Object { dotnet test $_.FullName --configuration Release --no-build if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dc815c..56ef07d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,5 +72,6 @@ install(FILES ${QUERIES} DESTINATION "${CMAKE_INSTALL_DATADIR}/tree-sitter/queries/expressif") add_custom_target(ts-test "${TREE_SITTER_CLI}" test + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMENT "tree-sitter test") diff --git a/Makefile b/Makefile index baa9b7b..ae0e676 100644 --- a/Makefile +++ b/Makefile @@ -110,7 +110,7 @@ uninstall: clean: $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) lib$(LANGUAGE_NAME).dll.a -test: +test: $(PARSER) $(TS) test .PHONY: all install uninstall clean test diff --git a/bindings/node/index.d.ts b/bindings/node/index.d.ts index 473dd42..b92873f 100644 --- a/bindings/node/index.d.ts +++ b/bindings/node/index.d.ts @@ -12,10 +12,13 @@ type ChildNode = { type NodeInfo = | (BaseNode & { subtypes: BaseNode[]; + fields?: never; + children?: never; }) | (BaseNode & { - fields: { [name: string]: ChildNode }; - children: ChildNode[]; + subtypes?: never; + fields?: { [name: string]: ChildNode }; + children?: ChildNode; }); /** diff --git a/grammar.js b/grammar.js index 1b68b46..88cf0cf 100644 --- a/grammar.js +++ b/grammar.js @@ -12,6 +12,13 @@ export default grammar({ extras: ($) => [/[\s\uFEFF\u2060\u200B]/], + supertypes: ($) => [ + $.value, + $.quoted_literal, + $.temporal_literal, + $.expression, + ], + rules: { source_file: ($) => $.root_expression, @@ -59,6 +66,8 @@ export default grammar({ $.backtick_quoted_literal, ), + // Quoted content is immediate and excludes CR/LF. This prevents global + // whitespace extras from making multiline quoted literals valid. double_quoted_literal: ($) => seq( '"', repeat(choice($.double_quoted_content, $.escape_sequence)), diff --git a/package.json b/package.json index c11838b..1b5c301 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ ], "scripts": { "generate": "tree-sitter generate", - "test": "tree-sitter test" + "test": "tree-sitter test && node --test bindings/node/binding_test.js" }, "repository": { "type": "git", diff --git a/pyproject.toml b/pyproject.toml index 46541b7..1635a6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ readme = "README.md" Homepage = "https://github.com/Seddryck/Expressif.Syntax" [project.optional-dependencies] -core = ["tree-sitter~=0.24"] +core = ["tree-sitter~=0.25"] [tool.cibuildwheel] build = "cp310-*" diff --git a/queries/highlights.scm b/queries/highlights.scm index e533c76..afe3cf3 100644 --- a/queries/highlights.scm +++ b/queries/highlights.scm @@ -2,6 +2,7 @@ (numeric_literal) @number (boolean_literal) @boolean +(escape_sequence) @string.escape [ (double_quoted_literal) @@ -13,3 +14,13 @@ (date_time_literal) (time_literal) ] @string.special + +[ + "|" + "," +] @punctuation.delimiter + +[ + "(" + ")" +] @punctuation.bracket diff --git a/src/grammar.json b/src/grammar.json index d540cf4..0deb1c5 100644 --- a/src/grammar.json +++ b/src/grammar.json @@ -339,6 +339,11 @@ "precedences": [], "externals": [], "inline": [], - "supertypes": [], + "supertypes": [ + "value", + "quoted_literal", + "temporal_literal", + "expression" + ], "reserved": {} } \ No newline at end of file diff --git a/src/node-types.json b/src/node-types.json index 171c1d5..e050ba1 100644 --- a/src/node-types.json +++ b/src/node-types.json @@ -1,4 +1,68 @@ [ + { + "type": "expression", + "named": true, + "subtypes": [ + { + "type": "function_call", + "named": true + } + ] + }, + { + "type": "quoted_literal", + "named": true, + "subtypes": [ + { + "type": "backtick_quoted_literal", + "named": true + }, + { + "type": "double_quoted_literal", + "named": true + } + ] + }, + { + "type": "temporal_literal", + "named": true, + "subtypes": [ + { + "type": "date_literal", + "named": true + }, + { + "type": "date_time_literal", + "named": true + }, + { + "type": "time_literal", + "named": true + } + ] + }, + { + "type": "value", + "named": true, + "subtypes": [ + { + "type": "boolean_literal", + "named": true + }, + { + "type": "numeric_literal", + "named": true + }, + { + "type": "quoted_literal", + "named": true + }, + { + "type": "temporal_literal", + "named": true + } + ] + }, { "type": "argument_list", "named": true, @@ -72,21 +136,6 @@ ] } }, - { - "type": "expression", - "named": true, - "fields": {}, - "children": { - "multiple": false, - "required": true, - "types": [ - { - "type": "function_call", - "named": true - } - ] - } - }, { "type": "function_call", "named": true, @@ -143,25 +192,6 @@ ] } }, - { - "type": "quoted_literal", - "named": true, - "fields": {}, - "children": { - "multiple": false, - "required": true, - "types": [ - { - "type": "backtick_quoted_literal", - "named": true - }, - { - "type": "double_quoted_literal", - "named": true - } - ] - } - }, { "type": "root_expression", "named": true, @@ -197,56 +227,6 @@ ] } }, - { - "type": "temporal_literal", - "named": true, - "fields": {}, - "children": { - "multiple": false, - "required": true, - "types": [ - { - "type": "date_literal", - "named": true - }, - { - "type": "date_time_literal", - "named": true - }, - { - "type": "time_literal", - "named": true - } - ] - } - }, - { - "type": "value", - "named": true, - "fields": {}, - "children": { - "multiple": false, - "required": true, - "types": [ - { - "type": "boolean_literal", - "named": true - }, - { - "type": "numeric_literal", - "named": true - }, - { - "type": "quoted_literal", - "named": true - }, - { - "type": "temporal_literal", - "named": true - } - ] - } - }, { "type": "\"", "named": false diff --git a/src/parser.c b/src/parser.c index 3f888e6..0198403 100644 --- a/src/parser.c +++ b/src/parser.c @@ -17,7 +17,7 @@ #define MAX_ALIAS_SEQUENCE_LENGTH 4 #define MAX_RESERVED_WORD_SET_SIZE 0 #define PRODUCTION_ID_COUNT 2 -#define SUPERTYPE_COUNT 0 +#define SUPERTYPE_COUNT 4 enum ts_symbol_identifiers { anon_sym_PIPE = 1, @@ -215,8 +215,9 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .named = true, }, [sym_expression] = { - .visible = true, + .visible = false, .named = true, + .supertype = true, }, [sym_function_call] = { .visible = true, @@ -231,16 +232,18 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .named = true, }, [sym_value] = { - .visible = true, + .visible = false, .named = true, + .supertype = true, }, [sym_boolean_literal] = { .visible = true, .named = true, }, [sym_quoted_literal] = { - .visible = true, + .visible = false, .named = true, + .supertype = true, }, [sym_double_quoted_literal] = { .visible = true, @@ -251,8 +254,9 @@ static const TSSymbolMetadata ts_symbol_metadata[] = { .named = true, }, [sym_temporal_literal] = { - .visible = true, + .visible = false, .named = true, + .supertype = true, }, [aux_sym_open_expression_repeat1] = { .visible = false, @@ -336,6 +340,37 @@ static const TSStateId ts_primary_state_ids[STATE_COUNT] = { [38] = 38, }; +static const TSSymbol ts_supertype_symbols[SUPERTYPE_COUNT] = { + sym_expression, + sym_quoted_literal, + sym_temporal_literal, + sym_value, +}; + +static const TSMapSlice ts_supertype_map_slices[] = { + [sym_expression] = {.index = 0, .length = 1}, + [sym_quoted_literal] = {.index = 1, .length = 2}, + [sym_temporal_literal] = {.index = 3, .length = 3}, + [sym_value] = {.index = 6, .length = 4}, +}; + +static const TSSymbol ts_supertype_map_entries[] = { + [0] = + sym_function_call, + [1] = + sym_backtick_quoted_literal, + sym_double_quoted_literal, + [3] = + sym_date_literal, + sym_date_time_literal, + sym_time_literal, + [6] = + sym_boolean_literal, + sym_numeric_literal, + sym_quoted_literal, + sym_temporal_literal, +}; + static bool ts_lex(TSLexer *lexer, TSStateId state) { START_LEXER(); eof = lexer->eof(lexer); @@ -1115,6 +1150,9 @@ TS_PUBLIC const TSLanguage *tree_sitter_expressif(void) { .field_names = ts_field_names, .field_map_slices = ts_field_map_slices, .field_map_entries = ts_field_map_entries, + .supertype_map_slices = ts_supertype_map_slices, + .supertype_map_entries = ts_supertype_map_entries, + .supertype_symbols = ts_supertype_symbols, .symbol_metadata = ts_symbol_metadata, .public_symbol_map = ts_symbol_map, .alias_map = ts_non_terminal_alias_map, diff --git a/test/corpus/expressions.txt b/test/corpus/expressions.txt index 143bc5f..fca4e9c 100644 --- a/test/corpus/expressions.txt +++ b/test/corpus/expressions.txt @@ -9,24 +9,25 @@ lower (source_file (root_expression (open_expression - (expression - (function_call - name: (function_name)))))) + (function_call + name: (function_name))))) ================== Empty parenthesized function call +:cst ================== lower() --- -(source_file - (root_expression - (open_expression - (expression - (function_call - name: (function_name)))))) +1:0 - 2:0 source_file +1:0 - 1:7 root_expression +1:0 - 1:7 open_expression +1:0 - 1:7 function_call +1:0 - 1:5 name: function_name `lower` +1:5 - 1:6 "(" +1:6 - 1:7 ")" ================== Mixed-case hyphenated function name @@ -39,9 +40,8 @@ foo-BAr-foo (source_file (root_expression (open_expression - (expression - (function_call - name: (function_name)))))) + (function_call + name: (function_name))))) ================== Function call with positional arguments @@ -54,16 +54,13 @@ example(10, 20) (source_file (root_expression (open_expression - (expression - (function_call - name: (function_name) - (argument_list - (positional_argument - (value - (numeric_literal))) - (positional_argument - (value - (numeric_literal))))))))) + (function_call + name: (function_name) + (argument_list + (positional_argument + (numeric_literal)) + (positional_argument + (numeric_literal))))))) ================== Open expression pipeline @@ -76,21 +73,16 @@ remove-chars("a") | upper | trim() (source_file (root_expression (open_expression - (expression - (function_call - name: (function_name) - (argument_list - (positional_argument - (value - (quoted_literal - (double_quoted_literal - (double_quoted_content)))))))) - (expression - (function_call - name: (function_name))) - (expression - (function_call - name: (function_name)))))) + (function_call + name: (function_name) + (argument_list + (positional_argument + (double_quoted_literal + (double_quoted_content))))) + (function_call + name: (function_name)) + (function_call + name: (function_name))))) ================== Numeric literals and pipeline @@ -103,15 +95,12 @@ Numeric literals and pipeline (source_file (root_expression (closed_expression - (value - (numeric_literal)) - (expression - (function_call - name: (function_name) - (argument_list - (positional_argument - (value - (numeric_literal))))))))) + (numeric_literal) + (function_call + name: (function_name) + (argument_list + (positional_argument + (numeric_literal))))))) ================== Boolean literals @@ -124,15 +113,12 @@ Boolean literals (source_file (root_expression (closed_expression - (value - (boolean_literal)) - (expression - (function_call - name: (function_name) - (argument_list - (positional_argument - (value - (boolean_literal))))))))) + (boolean_literal) + (function_call + name: (function_name) + (argument_list + (positional_argument + (boolean_literal))))))) ================== Bare boolean words are function calls @@ -145,12 +131,10 @@ true | false() (source_file (root_expression (open_expression - (expression - (function_call - name: (function_name))) - (expression - (function_call - name: (function_name)))))) + (function_call + name: (function_name)) + (function_call + name: (function_name))))) ================== Double-quoted text with escape @@ -163,14 +147,12 @@ Double-quoted text with escape (source_file (root_expression (closed_expression - (value - (quoted_literal - (double_quoted_literal - (double_quoted_content) - (escape_sequence) - (double_quoted_content) - (escape_sequence) - (double_quoted_content))))))) + (double_quoted_literal + (double_quoted_content) + (escape_sequence) + (double_quoted_content) + (escape_sequence) + (double_quoted_content))))) ================== Empty double-quoted text @@ -183,25 +165,24 @@ Empty double-quoted text (source_file (root_expression (closed_expression - (value - (quoted_literal - (double_quoted_literal)))))) + (double_quoted_literal)))) ================== Backtick text preserves punctuation and whitespace +:cst ================== ` foo, (bar) ` --- -(source_file - (root_expression - (closed_expression - (value - (quoted_literal - (backtick_quoted_literal - (backtick_quoted_content))))))) +1:0 - 2:0 source_file +1:0 - 1:14 root_expression +1:0 - 1:14 closed_expression +1:0 - 1:14 backtick_quoted_literal +1:0 - 1:1 "\`" +1:1 - 1:13 backtick_quoted_content ` foo, (bar) ` +1:13 - 1:14 "\`" ================== Date literal @@ -214,9 +195,7 @@ Date literal (source_file (root_expression (closed_expression - (value - (temporal_literal - (date_literal)))))) + (date_literal)))) ================== Datetime literal with T separator @@ -229,9 +208,7 @@ Datetime literal with T separator (source_file (root_expression (closed_expression - (value - (temporal_literal - (date_time_literal)))))) + (date_time_literal)))) ================== Datetime literal with space separator @@ -244,9 +221,7 @@ Datetime literal with space separator (source_file (root_expression (closed_expression - (value - (temporal_literal - (date_time_literal)))))) + (date_time_literal)))) ================== Time literal @@ -259,9 +234,7 @@ Time literal (source_file (root_expression (closed_expression - (value - (temporal_literal - (time_literal)))))) + (time_literal)))) ================== Temporal-looking quoted text remains text @@ -274,10 +247,8 @@ Temporal-looking quoted text remains text (source_file (root_expression (closed_expression - (value - (quoted_literal - (double_quoted_literal - (double_quoted_content))))))) + (double_quoted_literal + (double_quoted_content))))) ================== Whitespace is insignificant outside literals @@ -290,20 +261,57 @@ Whitespace is insignificant outside literals (source_file (root_expression (open_expression - (expression - (function_call - name: (function_name) - (argument_list - (positional_argument - (value - (numeric_literal))) - (positional_argument - (value - (numeric_literal)))))) - (expression - (function_call - name: (function_name) - (argument_list - (positional_argument - (value - (numeric_literal))))))))) + (function_call + name: (function_name) + (argument_list + (positional_argument + (numeric_literal)) + (positional_argument + (numeric_literal)))) + (function_call + name: (function_name) + (argument_list + (positional_argument + (numeric_literal))))))) + +================== +Backtick literal as a positional argument +================== + +remove-chars(`a`) + +--- + +(source_file + (root_expression + (open_expression + (function_call + (function_name) + (argument_list + (positional_argument + (backtick_quoted_literal + (backtick_quoted_content)))))))) + +================== +Temporal and backtick literals as positional arguments +================== + +foo(#"2025-12-17", #"2025-12-17T14:30:00", #"14:30:00", `bar`) + +--- + +(source_file + (root_expression + (open_expression + (function_call + (function_name) + (argument_list + (positional_argument + (date_literal)) + (positional_argument + (date_time_literal)) + (positional_argument + (time_literal)) + (positional_argument + (backtick_quoted_literal + (backtick_quoted_content)))))))) diff --git a/test/corpus/invalid.txt b/test/corpus/invalid.txt index c8d278f..e32e963 100644 --- a/test/corpus/invalid.txt +++ b/test/corpus/invalid.txt @@ -8,13 +8,11 @@ Function name cannot start with a digit (source_file (ERROR - (value - (numeric_literal))) + (numeric_literal)) (root_expression (open_expression - (expression - (function_call - (function_name)))))) + (function_call + (function_name))))) ================== Function name cannot contain a digit @@ -27,9 +25,8 @@ foo1 (source_file (root_expression (open_expression - (expression - (function_call - (function_name))))) + (function_call + (function_name)))) (ERROR (numeric_literal))) @@ -46,9 +43,8 @@ Function name cannot start with a hyphen (UNEXPECTED 'f')) (root_expression (open_expression - (expression - (function_call - (function_name)))))) + (function_call + (function_name))))) ================== Function name cannot end with a hyphen @@ -61,9 +57,8 @@ foo- (source_file (root_expression (open_expression - (expression - (function_call - (function_name))))) + (function_call + (function_name)))) (ERROR (UNEXPECTED '\n'))) @@ -81,9 +76,8 @@ foo--bar (UNEXPECTED '-')) (root_expression (open_expression - (expression - (function_call - (function_name)))))) + (function_call + (function_name))))) ================== Variables are not implemented @@ -98,9 +92,8 @@ Variables are not implemented (UNEXPECTED '@')) (root_expression (open_expression - (expression - (function_call - (function_name)))))) + (function_call + (function_name))))) ================== Unclosed function call @@ -113,14 +106,12 @@ foo(5 (source_file (root_expression (open_expression - (expression - (function_call - (function_name) - (argument_list - (positional_argument - (value - (numeric_literal)))) - (MISSING ")")))))) + (function_call + (function_name) + (argument_list + (positional_argument + (numeric_literal))) + (MISSING ")"))))) ================== Missing first argument @@ -133,14 +124,12 @@ foo(, 5) (source_file (root_expression (open_expression - (expression - (function_call - (function_name) - (ERROR) - (argument_list - (positional_argument - (value - (numeric_literal))))))))) + (function_call + (function_name) + (ERROR) + (argument_list + (positional_argument + (numeric_literal))))))) ================== Trailing argument separator @@ -153,14 +142,12 @@ foo(5,) (source_file (root_expression (open_expression - (expression - (function_call - (function_name) - (argument_list - (positional_argument - (value - (numeric_literal)))) - (ERROR)))))) + (function_call + (function_name) + (argument_list + (positional_argument + (numeric_literal))) + (ERROR))))) ================== Unquoted textual argument is rejected @@ -173,11 +160,10 @@ remove-chars(a) (source_file (root_expression (open_expression - (expression - (function_call - (function_name) - (ERROR - (function_name))))))) + (function_call + (function_name) + (ERROR + (function_name)))))) ================== Malformed date literal @@ -192,8 +178,7 @@ Malformed date literal (UNEXPECTED '-')) (root_expression (closed_expression - (value - (numeric_literal)))) + (numeric_literal))) (ERROR)) ================== @@ -209,10 +194,8 @@ Malformed datetime literal (UNEXPECTED '"')) (root_expression (closed_expression - (value - (quoted_literal - (double_quoted_literal - (MISSING """))))))) + (double_quoted_literal + (MISSING """))))) ================== Malformed time literal @@ -227,10 +210,8 @@ Malformed time literal (UNEXPECTED '"')) (root_expression (closed_expression - (value - (quoted_literal - (double_quoted_literal - (MISSING """))))))) + (double_quoted_literal + (MISSING """))))) ================== Boolean literal is case-sensitive @@ -245,6 +226,125 @@ Boolean literal is case-sensitive (UNEXPECTED 'T')) (root_expression (open_expression - (expression - (function_call + (function_call + (function_name))))) + +================== +Double-quoted literal cannot contain a newline +================== + +"first +second" + +--- + +(source_file + (root_expression + (closed_expression + (double_quoted_literal + (double_quoted_content) + (ERROR + (function_name)))))) + +================== +Backtick literal cannot contain a newline +================== + +`first +second` + +--- + +(source_file + (root_expression + (closed_expression + (backtick_quoted_literal + (backtick_quoted_content) + (ERROR + (function_name)))))) + +================== +Named arguments are not implemented +================== + +foo(a: 1) + +--- + +(source_file + (root_expression + (open_expression + (function_call + (function_name) + (ERROR + (function_name) + (UNEXPECTED ':')) + (argument_list + (positional_argument + (numeric_literal))))))) + +================== +Spread arguments are not implemented +================== + +foo(...a) + +--- + +(source_file + (root_expression + (open_expression + (function_call + (function_name) + (ERROR + (UNEXPECTED '.') (function_name)))))) + +================== +Operators are not implemented +================== + +1 + 2 + +--- + +(source_file + (ERROR + (numeric_literal) + (UNEXPECTED '+')) + (root_expression + (closed_expression + (numeric_literal)))) + +================== +Property references are not implemented +================== + +[foo] + +--- + +(source_file + (ERROR + (UNEXPECTED '[')) + (root_expression + (open_expression + (function_call + (function_name)))) + (ERROR + (UNEXPECTED ']'))) + +================== +Index references are not implemented +================== + +#1 + +--- + +(source_file + (ERROR + (UNEXPECTED '1')) + (root_expression + (closed_expression + (numeric_literal))))