├── .editorconfig ├── .gitattributes ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── binding.gyp ├── bindings ├── c │ ├── tree-sitter-ini.h │ └── tree-sitter-ini.pc.in ├── go │ ├── binding.go │ └── binding_test.go ├── node │ ├── binding.cc │ ├── binding_test.js │ ├── index.d.ts │ └── index.js ├── python │ ├── tests │ │ └── test_binding.py │ └── tree_sitter_ini │ │ ├── __init__.py │ │ ├── __init__.pyi │ │ ├── binding.c │ │ └── py.typed ├── rust │ ├── build.rs │ └── lib.rs └── swift │ ├── TreeSitterIni │ └── ini.h │ └── TreeSitterIniTests │ └── TreeSitterIniTests.swift ├── go.mod ├── grammar.js ├── notes.md ├── package-lock.json ├── package.json ├── pyproject.toml ├── queries ├── folds.scm └── highlights.scm ├── setup.py ├── src ├── grammar.json ├── node-types.json ├── parser.c └── tree_sitter │ ├── alloc.h │ ├── array.h │ └── parser.h ├── test-awsconfig └── test └── corpus └── main.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{json,toml,yml,gyp}] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.js] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.txt] 18 | # Test/corpus files may intentionally have trailing whitespace. 19 | trim_trailing_whitespace = false 20 | 21 | [*.rs] 22 | indent_style = space 23 | indent_size = 4 24 | 25 | [*.{c,cc,h}] 26 | indent_style = space 27 | indent_size = 4 28 | 29 | [*.{py,pyi}] 30 | indent_style = space 31 | indent_size = 4 32 | 33 | [*.swift] 34 | indent_style = space 35 | indent_size = 4 36 | 37 | [*.go] 38 | indent_style = tab 39 | indent_size = 8 40 | 41 | [Makefile] 42 | indent_style = tab 43 | indent_size = 8 44 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | src/*.json linguist-generated 4 | src/parser.c linguist-generated 5 | src/tree_sitter/* linguist-generated 6 | 7 | bindings/** linguist-generated 8 | binding.gyp linguist-generated 9 | setup.py linguist-generated 10 | Makefile linguist-generated 11 | Package.swift linguist-generated 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust artifacts 2 | target/ 3 | 4 | # Node artifacts 5 | build/ 6 | prebuilds/ 7 | node_modules/ 8 | 9 | # Swift artifacts 10 | .build/ 11 | 12 | # Go artifacts 13 | _obj/ 14 | 15 | # Python artifacts 16 | .venv/ 17 | dist/ 18 | *.egg-info 19 | *.whl 20 | 21 | # C artifacts 22 | *.a 23 | *.so 24 | *.so.* 25 | *.dylib 26 | *.dll 27 | *.pc 28 | 29 | # Example dirs 30 | /examples/*/ 31 | 32 | # Grammar volatiles 33 | *.wasm 34 | *.obj 35 | *.o 36 | 37 | # Archives 38 | *.tar.gz 39 | *.tgz 40 | *.zip 41 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tree-sitter-ini" 3 | description = "ini grammar for the tree-sitter parsing library" 4 | version = "1.3.0" 5 | keywords = ["incremental", "parsing", "ini"] 6 | categories = ["parsing", "text-editors"] 7 | repository = "https://github.com/justinmk/tree-sitter-ini" 8 | edition = "2018" 9 | license = "MIT" 10 | 11 | build = "bindings/rust/build.rs" 12 | include = [ 13 | "bindings/rust/*", 14 | "grammar.js", 15 | "queries/*", 16 | "src/*", 17 | ] 18 | 19 | [lib] 20 | path = "bindings/rust/lib.rs" 21 | 22 | [dependencies] 23 | tree-sitter-language = "0.1.0" 24 | 25 | [build-dependencies] 26 | cc = "1.0" 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := 0.0.1 2 | 3 | LANGUAGE_NAME := tree-sitter-ini 4 | 5 | # repository 6 | SRC_DIR := src 7 | 8 | PARSER_REPO_URL := $(shell git -C $(SRC_DIR) remote get-url origin 2>/dev/null) 9 | 10 | ifeq ($(PARSER_URL),) 11 | PARSER_URL := $(subst .git,,$(PARSER_REPO_URL)) 12 | ifeq ($(shell echo $(PARSER_URL) | grep '^[a-z][-+.0-9a-z]*://'),) 13 | PARSER_URL := $(subst :,/,$(PARSER_URL)) 14 | PARSER_URL := $(subst git@,https://,$(PARSER_URL)) 15 | endif 16 | endif 17 | 18 | TS ?= tree-sitter 19 | 20 | # ABI versioning 21 | SONAME_MAJOR := $(word 1,$(subst ., ,$(VERSION))) 22 | SONAME_MINOR := $(word 2,$(subst ., ,$(VERSION))) 23 | 24 | # install directory layout 25 | PREFIX ?= /usr/local 26 | INCLUDEDIR ?= $(PREFIX)/include 27 | LIBDIR ?= $(PREFIX)/lib 28 | PCLIBDIR ?= $(LIBDIR)/pkgconfig 29 | 30 | # source/object files 31 | PARSER := $(SRC_DIR)/parser.c 32 | EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c)) 33 | OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS)) 34 | 35 | # flags 36 | ARFLAGS ?= rcs 37 | override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC 38 | 39 | # OS-specific bits 40 | ifeq ($(OS),Windows_NT) 41 | $(error "Windows is not supported") 42 | else ifeq ($(shell uname),Darwin) 43 | SOEXT = dylib 44 | SOEXTVER_MAJOR = $(SONAME_MAJOR).dylib 45 | SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).dylib 46 | LINKSHARED := $(LINKSHARED)-dynamiclib -Wl, 47 | ifneq ($(ADDITIONAL_LIBS),) 48 | LINKSHARED := $(LINKSHARED)$(ADDITIONAL_LIBS), 49 | endif 50 | LINKSHARED := $(LINKSHARED)-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SONAME_MAJOR).dylib,-rpath,@executable_path/../Frameworks 51 | else 52 | SOEXT = so 53 | SOEXTVER_MAJOR = so.$(SONAME_MAJOR) 54 | SOEXTVER = so.$(SONAME_MAJOR).$(SONAME_MINOR) 55 | LINKSHARED := $(LINKSHARED)-shared -Wl, 56 | ifneq ($(ADDITIONAL_LIBS),) 57 | LINKSHARED := $(LINKSHARED)$(ADDITIONAL_LIBS) 58 | endif 59 | LINKSHARED := $(LINKSHARED)-soname,lib$(LANGUAGE_NAME).so.$(SONAME_MAJOR) 60 | endif 61 | ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),) 62 | PCLIBDIR := $(PREFIX)/libdata/pkgconfig 63 | endif 64 | 65 | all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc 66 | 67 | lib$(LANGUAGE_NAME).a: $(OBJS) 68 | $(AR) $(ARFLAGS) $@ $^ 69 | 70 | lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS) 71 | $(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@ 72 | ifneq ($(STRIP),) 73 | $(STRIP) $@ 74 | endif 75 | 76 | $(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in 77 | sed -e 's|@URL@|$(PARSER_URL)|' \ 78 | -e 's|@VERSION@|$(VERSION)|' \ 79 | -e 's|@LIBDIR@|$(LIBDIR)|' \ 80 | -e 's|@INCLUDEDIR@|$(INCLUDEDIR)|' \ 81 | -e 's|@REQUIRES@|$(REQUIRES)|' \ 82 | -e 's|@ADDITIONAL_LIBS@|$(ADDITIONAL_LIBS)|' \ 83 | -e 's|=$(PREFIX)|=$${prefix}|' \ 84 | -e 's|@PREFIX@|$(PREFIX)|' $< > $@ 85 | 86 | $(PARSER): $(SRC_DIR)/grammar.json 87 | $(TS) generate --no-bindings $^ 88 | 89 | install: all 90 | install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)' 91 | install -m644 bindings/c/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h 92 | install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc 93 | install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a 94 | install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) 95 | ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) 96 | ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) 97 | 98 | uninstall: 99 | $(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \ 100 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \ 101 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \ 102 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \ 103 | '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \ 104 | '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc 105 | 106 | clean: 107 | $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) 108 | 109 | test: 110 | $(TS) test 111 | 112 | .PHONY: all install uninstall clean test 113 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "TreeSitterIni", 6 | products: [ 7 | .library(name: "TreeSitterIni", targets: ["TreeSitterIni"]), 8 | ], 9 | dependencies: [], 10 | targets: [ 11 | .target(name: "TreeSitterIni", 12 | path: ".", 13 | exclude: [ 14 | "Cargo.toml", 15 | "Makefile", 16 | "binding.gyp", 17 | "bindings/c", 18 | "bindings/go", 19 | "bindings/node", 20 | "bindings/python", 21 | "bindings/rust", 22 | "prebuilds", 23 | "grammar.js", 24 | "package.json", 25 | "package-lock.json", 26 | "pyproject.toml", 27 | "setup.py", 28 | "test", 29 | "examples", 30 | ".editorconfig", 31 | ".github", 32 | ".gitignore", 33 | ".gitattributes", 34 | ".gitmodules", 35 | ], 36 | sources: [ 37 | "src/parser.c", 38 | // NOTE: if your language has an external scanner, add it here. 39 | ], 40 | resources: [ 41 | .copy("queries") 42 | ], 43 | publicHeadersPath: "bindings/swift", 44 | cSettings: [.headerSearchPath("src")]) 45 | ], 46 | cLanguageStandard: .c11 47 | ) 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tree-sitter-ini 2 | ================== 3 | 4 | This grammar implements the [INI format](https://en.wikipedia.org/wiki/INI_file). 5 | 6 | Overview 7 | -------- 8 | 9 | Example INI file: 10 | 11 | ```ini 12 | [section 1] 13 | some_key = some_value 14 | another-key = another value 15 | 16 | [another section] 17 | # Keys may contain whitespace. 18 | key 1 = value 1 19 | # Value may be empty. 20 | key_2 = 21 | ``` 22 | 23 | See [test/corpus/](./test/corpus/) for more examples. 24 | 25 | Notes 26 | ----- 27 | 28 | - Section name must appear on a line by itself. 29 | - Equals sign (=) and semicolon (;) are [reserved characters](https://en.wikipedia.org/wiki/INI_file#Key-value_pairs) 30 | and cannot appear in the key. Any whitespace surrounding the key is stripped. 31 | - Comments (`;` or `#`) must start at column 1. Trailing comments are not supported yet. [#13](https://github.com/justinmk/tree-sitter-ini/issues/13) 32 | - Duplicate names are not checked. 33 | - Line continuations (`\`) are not supported. 34 | - `setting_value` includes whitespace. [#3](https://github.com/justinmk/tree-sitter-ini/issues/3). 35 | Should values exclude surrounding whitespace? 36 | - [Quoted keys/values](https://en.wikipedia.org/wiki/INI_file#Quoted_values) are not supported yet. 37 | - Escape sequences are not supported. 38 | 39 | Reference 40 | --------- 41 | 42 | - https://en.wikipedia.org/wiki/INI_file 43 | - https://github.com/textmate/ini.tmbundle 44 | 45 | Release 46 | ------- 47 | 48 | Steps to perform a release: 49 | 50 | 1. Bump and tag the version (choose `patch`/`minor`/`major` as appropriate). 51 | ``` 52 | npm version patch -m "release %s" 53 | ``` 54 | 2. Bump to prerelease, without creating a tag . 55 | ``` 56 | npm version --no-git-tag-version prerelease --preid dev && git add package*.json && git commit -m bump 57 | ``` 58 | 3. Push. 59 | ``` 60 | git push --follow-tags 61 | ``` 62 | 4. Release the tagged commit: https://github.com/justinmk/tree-sitter-ini/releases/new 63 | 64 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "tree_sitter_ini_binding", 5 | "dependencies": [ 6 | " 2 | 3 | typedef struct TSLanguage TSLanguage; 4 | 5 | extern "C" TSLanguage *tree_sitter_ini(); 6 | 7 | // "tree-sitter", "language" hashed with BLAKE2 8 | const napi_type_tag LANGUAGE_TYPE_TAG = { 9 | 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 10 | }; 11 | 12 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 13 | exports["name"] = Napi::String::New(env, "ini"); 14 | auto language = Napi::External::New(env, tree_sitter_ini()); 15 | language.TypeTag(&LANGUAGE_TYPE_TAG); 16 | exports["language"] = language; 17 | return exports; 18 | } 19 | 20 | NODE_API_MODULE(tree_sitter_ini_binding, Init) 21 | -------------------------------------------------------------------------------- /bindings/node/binding_test.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const assert = require("node:assert"); 4 | const { test } = require("node:test"); 5 | 6 | test("can load grammar", () => { 7 | const parser = new (require("tree-sitter"))(); 8 | assert.doesNotThrow(() => parser.setLanguage(require("."))); 9 | }); 10 | -------------------------------------------------------------------------------- /bindings/node/index.d.ts: -------------------------------------------------------------------------------- 1 | type BaseNode = { 2 | type: string; 3 | named: boolean; 4 | }; 5 | 6 | type ChildNode = { 7 | multiple: boolean; 8 | required: boolean; 9 | types: BaseNode[]; 10 | }; 11 | 12 | type NodeInfo = 13 | | (BaseNode & { 14 | subtypes: BaseNode[]; 15 | }) 16 | | (BaseNode & { 17 | fields: { [name: string]: ChildNode }; 18 | children: ChildNode[]; 19 | }); 20 | 21 | type Language = { 22 | name: string; 23 | language: unknown; 24 | nodeTypeInfo: NodeInfo[]; 25 | }; 26 | 27 | declare const language: Language; 28 | export = language; 29 | -------------------------------------------------------------------------------- /bindings/node/index.js: -------------------------------------------------------------------------------- 1 | const root = require("path").join(__dirname, "..", ".."); 2 | 3 | module.exports = require("node-gyp-build")(root); 4 | 5 | try { 6 | module.exports.nodeTypeInfo = require("../../src/node-types.json"); 7 | } catch (_) {} 8 | -------------------------------------------------------------------------------- /bindings/python/tests/test_binding.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import tree_sitter, tree_sitter_ini 4 | 5 | 6 | class TestLanguage(TestCase): 7 | def test_can_load_grammar(self): 8 | try: 9 | tree_sitter.Language(tree_sitter_ini.language()) 10 | except Exception: 11 | self.fail("Error loading Ini grammar") 12 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_ini/__init__.py: -------------------------------------------------------------------------------- 1 | "Ini grammar for tree-sitter" 2 | 3 | from ._binding import language 4 | 5 | __all__ = ["language"] 6 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_ini/__init__.pyi: -------------------------------------------------------------------------------- 1 | def language() -> int: ... 2 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_ini/binding.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct TSLanguage TSLanguage; 4 | 5 | TSLanguage *tree_sitter_ini(void); 6 | 7 | static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { 8 | return PyCapsule_New(tree_sitter_ini(), "tree_sitter.Language", NULL); 9 | } 10 | 11 | static PyMethodDef methods[] = { 12 | {"language", _binding_language, METH_NOARGS, 13 | "Get the tree-sitter language for this grammar."}, 14 | {NULL, NULL, 0, NULL} 15 | }; 16 | 17 | static struct PyModuleDef module = { 18 | .m_base = PyModuleDef_HEAD_INIT, 19 | .m_name = "_binding", 20 | .m_doc = NULL, 21 | .m_size = -1, 22 | .m_methods = methods 23 | }; 24 | 25 | PyMODINIT_FUNC PyInit__binding(void) { 26 | return PyModule_Create(&module); 27 | } 28 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_ini/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinmk/tree-sitter-ini/0e152b17fc0e929dd71f4bc4e317b3421ed39029/bindings/python/tree_sitter_ini/py.typed -------------------------------------------------------------------------------- /bindings/rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let src_dir = std::path::Path::new("src"); 3 | 4 | let mut c_config = cc::Build::new(); 5 | c_config.include(&src_dir); 6 | c_config 7 | .flag_if_supported("-Wno-unused-parameter") 8 | .flag_if_supported("-Wno-unused-but-set-variable") 9 | .flag_if_supported("-Wno-trigraphs"); 10 | #[cfg(target_env = "msvc")] 11 | c_config.flag("-utf-8"); 12 | 13 | let parser_path = src_dir.join("parser.c"); 14 | c_config.file(&parser_path); 15 | 16 | // If your language uses an external scanner written in C, 17 | // then include this block of code: 18 | 19 | /* 20 | let scanner_path = src_dir.join("scanner.c"); 21 | c_config.file(&scanner_path); 22 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); 23 | */ 24 | 25 | c_config.compile("parser"); 26 | println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); 27 | 28 | // If your language uses an external scanner written in C++, 29 | // then include this block of code: 30 | 31 | /* 32 | let mut cpp_config = cc::Build::new(); 33 | cpp_config.cpp(true); 34 | cpp_config.include(&src_dir); 35 | cpp_config 36 | .flag_if_supported("-Wno-unused-parameter") 37 | .flag_if_supported("-Wno-unused-but-set-variable"); 38 | let scanner_path = src_dir.join("scanner.cc"); 39 | cpp_config.file(&scanner_path); 40 | cpp_config.compile("scanner"); 41 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); 42 | */ 43 | } 44 | -------------------------------------------------------------------------------- /bindings/rust/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides Ini language support for the [tree-sitter][] parsing library. 2 | //! 3 | //! Typically, you will use the [language][language func] function to add this language to a 4 | //! tree-sitter [Parser][], and then use the parser to parse some code: 5 | //! 6 | //! ``` 7 | //! let code = r#" 8 | //! "#; 9 | //! let mut parser = tree_sitter::Parser::new(); 10 | //! let language = tree_sitter_ini::LANGUAGE; 11 | //! parser 12 | //! .set_language(&language.into()) 13 | //! .expect("Error loading Ini parser"); 14 | //! let tree = parser.parse(code, None).unwrap(); 15 | //! assert!(!tree.root_node().has_error()); 16 | //! ``` 17 | //! 18 | //! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html 19 | //! [language func]: fn.language.html 20 | //! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html 21 | //! [tree-sitter]: https://tree-sitter.github.io/ 22 | 23 | use tree_sitter_language::LanguageFn; 24 | 25 | extern "C" { 26 | fn tree_sitter_ini() -> *const (); 27 | } 28 | 29 | /// The tree-sitter [`LanguageFn`] for this grammar. 30 | pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_ini) }; 31 | 32 | /// The content of the [`node-types.json`][] file for this grammar. 33 | /// 34 | /// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types 35 | pub const NODE_TYPES: &str = include_str!("../../src/node-types.json"); 36 | 37 | // NOTE: uncomment these to include any queries that this grammar contains: 38 | 39 | // pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm"); 40 | // pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm"); 41 | // pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm"); 42 | // pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm"); 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | #[test] 47 | fn test_can_load_grammar() { 48 | let mut parser = tree_sitter::Parser::new(); 49 | parser 50 | .set_language(&super::LANGUAGE.into()) 51 | .expect("Error loading Ini parser"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bindings/swift/TreeSitterIni/ini.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_INI_H_ 2 | #define TREE_SITTER_INI_H_ 3 | 4 | typedef struct TSLanguage TSLanguage; 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | const TSLanguage *tree_sitter_ini(void); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif // TREE_SITTER_INI_H_ 17 | -------------------------------------------------------------------------------- /bindings/swift/TreeSitterIniTests/TreeSitterIniTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import SwiftTreeSitter 3 | import TreeSitterIni 4 | 5 | final class TreeSitterIniTests: XCTestCase { 6 | func testCanLoadGrammar() throws { 7 | let parser = Parser() 8 | let language = Language(language: tree_sitter_ini()) 9 | XCTAssertNoThrow(try parser.setLanguage(language), 10 | "Error loading Ini grammar") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tree-sitter/tree-sitter-ini 2 | 3 | go 1.23 4 | 5 | require github.com/tree-sitter/go-tree-sitter v0.23 6 | -------------------------------------------------------------------------------- /grammar.js: -------------------------------------------------------------------------------- 1 | /// 2 | // @ts-check 3 | 4 | module.exports = grammar({ 5 | name: 'ini', 6 | 7 | extras: $ => [ 8 | $.comment, 9 | $._blank, 10 | /[\t ]/ 11 | ], 12 | 13 | rules: { 14 | document: $ => seq( 15 | repeat($._blank), // Eat blank lines at top of file. 16 | optional(repeat(seq($.setting))), 17 | repeat($.section), 18 | ), 19 | 20 | // Section has: 21 | // - a title 22 | // - zero or more settings (name=value pairs) 23 | // - comments (optional) 24 | section: $ => prec.left(seq( 25 | $.section_name, 26 | repeat(seq( 27 | $.setting, 28 | )), 29 | )), 30 | 31 | section_name: $ => seq( 32 | '[', 33 | alias(/[^\[\]]+/, $.text), 34 | ']', 35 | /\r?\n/, 36 | ), 37 | 38 | setting: $ => seq( 39 | alias(/[^;#=\s\[]+( *[^;#=\s\[])*/, $.setting_name), 40 | '=', 41 | optional(alias(/.+/, $.setting_value)), 42 | /\r?\n/, 43 | ), 44 | 45 | comment: $ => seq(/[;#]/, alias(/[^\r\n]*/, $.text), /\r?\n/), 46 | 47 | _blank: () => field('blank', /\r?\n/), 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | # treesitter ini, aws-config 2 | 3 | This is a tree-sitter experience report. 4 | 5 | ## Why treesitter? 6 | 7 | > Tree-sitter uses context-aware tokenization - in a given parse state, 8 | > Tree-sitter only recognizes tokens that are syntactically valid in that 9 | > state. This is what allows Tree-sitter to tokenize languages correctly 10 | > without requiring the grammar author to think about different lexer modes. 11 | — [ref](https://github.com/tree-sitter/tree-sitter/pull/1635) 12 | 13 | tree-sitter has nice docs to walkthrough this: https://tree-sitter.github.io/tree-sitter/creating-parsers 14 | 15 | https://siraben.dev/2022/03/01/tree-sitter.html is another good tutorial and provides some motivation for treesitter: 16 | 17 | ## Experience report 18 | 19 | "tree-sitter" should be named "treesitter". There is zero reason to choose a multi-word name if you can avoid it. Multi-word names manifest in various forms depending on the context: "treeSitter", "TreeSitter", "tree_sitter", "tree-sitter". This is worthless entropy. 20 | 21 | Here's the C code for an `.ini` parser generated by tree-sitter. It has an API 22 | exposed as a C dynamic library, usable from any language. How did I do this? See below. 23 | 24 | #include 25 | … 26 | enum { 27 | … 28 | sym_setting_name = 6, 29 | sym_setting_value = 7, 30 | … 31 | }; 32 | static const char *ts_symbol_names[] = { 33 | [sym_setting_name] = "setting_name", 34 | [sym_setting_value] = "setting_value", 35 | … 36 | }; 37 | static bool ts_lex(TSLexer *lexer, TSStateId state) { 38 | START_LEXER(); 39 | eof = lexer->eof(lexer); 40 | switch (state) { 41 | case 0: 42 | … 43 | END_STATE(); 44 | … 45 | case 8: 46 | ACCEPT_TOKEN(aux_sym_section_name_token1); 47 | if (lookahead == '\n') ADVANCE(10); 48 | if (lookahead == '[' || 49 | lookahead == ']') ADVANCE(19); 50 | if (lookahead != 0) ADVANCE(8); 51 | END_STATE(); 52 | … 53 | case 14: 54 | ACCEPT_TOKEN(sym_setting_name); 55 | if (lookahead == '\n') ADVANCE(16); 56 | if (lookahead == '=') ADVANCE(19); 57 | if (lookahead != 0) ADVANCE(14); 58 | END_STATE(); 59 | … 60 | } 61 | } 62 | static uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { 63 | [0] = { 64 | … 65 | }, 66 | [1] = { 67 | … 68 | [sym_section] = STATE(3), 69 | [sym_section_name] = STATE(2), 70 | }, 71 | }; 72 | 73 | 74 | Creating a new parser for the INI file type 75 | ------------------------------------------- 76 | 77 | $ npm init 78 | 79 | # Install a small module that lets your parser be used from Node. 80 | $ npm install --save nan 81 | 82 | # Install (and depend-on) the Tree-sitter CLI. 83 | $ npm install --save-dev tree-sitter-cli 84 | 85 | Create the grammar.js stub 86 | -------------------------- 87 | 88 | `grammar.js`: 89 | 90 | module.exports = grammar({ 91 | name: 'ini', 92 | rules: { 93 | // TODO: add the actual grammar rules 94 | source_file: $ => 'hello' 95 | } 96 | }); 97 | 98 | Generate the parser 99 | ------------------- 100 | 101 | $ ./node_modules/.bin/tree-sitter generate 102 | 103 | That generates `src/parser.c` from the `grammar.js` that we defined (typically 104 | tree-sitter projects put this in their `npm run build` script in 105 | `package.json`). The parser can be tested now: 106 | 107 | $ echo hello > test 108 | $ ./node_modules/.bin/tree-sitter parse test 109 | (source_file [0, 0] - [1, 0]) 110 | 111 | It also generates some glue files to compile the parser's C source a library 112 | and load it as a Node.js module. Cool! 113 | 114 | - `binding.gyp` tells Node.js how to compile the parser's C source. 115 | - `tree-sitter generate` automatically compiles `parser.c` into 116 | a dynamically-loadable library. 117 | - `index.js` is the entrypoint for Node.js to load the compiled C module. 118 | - `src/binding.cc` wraps your parser in a JavaScript object for Node.js 119 | - `src/tree_sitter/parser.h` is a C header by `parser.c`. 120 | 121 | Tree-sitter will detect ambiguity. For example this `grammar.js` is ambiguous: 122 | 123 | module.exports = grammar({ 124 | name: 'ini', 125 | rules: { 126 | document: $ => $._value, 127 | _value: $ => choice( 128 | $.a, 129 | $.b, 130 | ), 131 | a: $ => 'hello', 132 | b: $ => 'hello' 133 | } 134 | }); 135 | 136 | Tree-sitter throws a `Unresolved conflict` error when it detects ambiguity: 137 | 138 | $ ./node_modules/.bin/tree-sitter generate 139 | 140 | Unresolved conflict for symbol sequence: 141 | 142 | 'hello' • EOF … 143 | 144 | Possible interpretations: 145 | 146 | 1: (a 'hello') • EOF … 147 | 2: (b 'hello') • EOF … 148 | 149 | Possible resolutions: 150 | 151 | 1: Specify a higher precedence in `a` than in the other rules. 152 | 2: Specify a higher precedence in `b` than in the other rules. 153 | 3: Add a conflict for these rules: `a`, `b` 154 | 155 | Developing the grammar, part 1 156 | ------------------------------ 157 | 158 | One of my favorite things about tree-sitter is its use of S-expressions 159 | ("sexps") to **define test-cases** and **pretty-print** the AST. S-expressions 160 | are the human-readable representation of the AST. They are similar to 161 | HTML/XML/SGML (but simpler and older): just a very simple way to express a tree 162 | of things. 163 | 164 | Here's the first test for my "ini" grammar: 165 | 166 | ================== 167 | Test 1 168 | ================== 169 | 170 | foo = bar 171 | 172 | --- 173 | 174 | () 175 | 176 | It fails: 177 | 178 | $ ./node_modules/.bin/tree-sitter test 179 | main: 180 | ✗ Test 1 181 | 182 | 1 failure: 183 | 184 | expected / actual 185 | 186 | 1. Test 1: 187 | (ERROR (UNEXPECTED 'f')) () 188 | (ERROR (UNEXPECTED 'f')) () 189 | 190 | Now I can start developing the grammar. Tree-sitter provides 191 | a [DSL](https://tree-sitter.github.io/tree-sitter/creating-parsers#the-grammar-dsl) 192 | to define grammars. 193 | 194 | - `name`: Name of the grammar. 195 | - `rules`: 196 | - Symbols (the `$` object): Use `$.foo` to another grammar symbol within a rule. 197 | - String and Regex literals: terminal symbols. 198 | - Sequences: `seq(rule1, rule2, …)`: matches rules in the given order. 199 | - Alternatives: `choice(rule1, rule2, …)`: matches any one rule in the set. 200 | - Repetitions: `repeat(rule)` matches _zero-or-more_ rules. 201 | - `repeat1(rule)` matches _one-or-more_ rules. 202 | - `optional(rule)` matches _zero-or-one_ rule. 203 | - Precedence: `prec(number, rule)` marks a rule with a precedence to resolve 204 | ambiguities ("LR(1) conflicts") 205 | - `prec.left([number], rule)` marks a rule as _left-associative_, i.e. 206 | prefers a match that ends _earlier_. 207 | - `prec.right([number], rule)` prefers a match that ends _later_. 208 | - `prec.dynamic(number, rule)` decides precedence at runtime! Uses the 209 | `conflicts` field in the grammar, when there is a "genuine ambiguity" 210 | (multiple matching rules). 211 | - `token(rule)` marks a rule as producing a single token; the entire rule 212 | subtree will be handled atomically by the lexer, as if you had written it 213 | all using a single regular expression. 214 | - Its content will be represented in the final syntax tree as a single leaf node. 215 | (Whereas If you don't use TOKEN, there will be a separate leaf node in 216 | the tree for each string literal or regexp in the subtree.) 217 | - `alias(rule, name)` renames a rule in the AST. 218 | - Example: `alias($.foo, $.bar)` appears as _named_ node `bar` 219 | - Example: `alias($.foo, 'bar')` appears as _anonymous_ node "bar". 220 | - `field(name, rule)` assigns a _field name_ to the matching child nodes, which 221 | can be used to access the nodes from the AST. 222 | - `extras`: array of tokens that may appear _anywhere_. Often used for 223 | whitespace and comments. Example (also the default!): 224 | ``` 225 | extras: $ => [ 226 | /\s/ 227 | ], 228 | ``` 229 | - `inline`: useful for rules used in multiple places but for which you don’t 230 | want AST nodes. 231 | - `conflicts`: defines _intentional_ ambiguities (LR(1) conflicts), which 232 | tree-sitter will resolve at _runtime_ (dynamic precedence). 233 | - `externals`: token names which can be returned by an external scanner. 234 | Allows you to write custom C code which runs during the lexing process to 235 | handle non-regular lexical rules (e.g. Python indentation). 236 | - `word`: matches keywords for keyword extraction optimization. 237 | - `supertypes`: hidden rules considered to be ‘supertypes’ in the generated 238 | _node types_ file (useful for statically-typed languages) 239 | 240 | Reading about `seq()` gives me enough to try a toy rule for the "ini" grammar: 241 | 242 | - `grammar.js`: 243 | ``` 244 | module.exports = grammar({ 245 | name: 'ini', 246 | rules: { 247 | document: $ => $._value, 248 | _value: $ => choice( 249 | $.kvpair, 250 | ), 251 | kvpair: $ => seq( 252 | /[^=]+/, '=', /.+/ 253 | ), 254 | } 255 | }); 256 | ``` 257 | - `test/corpus/main.txt`: 258 | ``` 259 | ================== 260 | Test 1 261 | ================== 262 | 263 | foo = bar 264 | 265 | --- 266 | 267 | (document (kvpair)) 268 | ``` 269 | 270 | The test passes... 271 | 272 | $ ./node_modules/.bin/tree-sitter generate && ./node_modules/.bin/tree-sitter test 273 | main: 274 | ✓ Test 1 275 | 276 | ...but this initial grammar lacks granularity: it doesn't parse the individual 277 | parts of `kvpair`, i.e. we probably want "key" and "value". And it feels wrong 278 | to use regex to exclude "=", that will probably be avoided more "formally" in 279 | the final grammar. 280 | 281 | In fact [this section](https://tree-sitter.github.io/tree-sitter/creating-parsers#writing-the-grammar) 282 | of the docs mentions two properties to write a good parser: 283 | 284 | 1. _Intuitive structure._ Because the grammar directly influences the structure 285 | of the AST! 286 | 2. _Adherence to LR(1)._ For optimal performance. 287 | 288 | Developing the grammar, part 2 289 | ------------------------------ 290 | 291 | Now that I have a toy grammar feeding into a passing test, I am ready to experiment. 292 | 293 | - I see [tree-sitter-json grammar](https://github.com/tree-sitter/tree-sitter-json/blob/d3976b27df8622ed17bef6dd5e358b398e73c676/grammar.js#L54) 294 | combines `seq(optional(choice(…)))` in a rule--even assigning rules to local 295 | variables--and return a single rule via `return token(…)`. 296 | - I see that [tree-sitter-javascript](https://github.com/tree-sitter/tree-sitter-javascript/blob/a2e3d72f82716a20982ecc06c349c54ec40d54df/grammar.js#L35) 297 | includes comments in its `extra` directive. 298 | 299 | ini files are very simple: 300 | 301 | - they have three parts: section, settings, and comments 302 | ``` 303 | [section 1] 304 | a = val1 305 | b = val2 306 | # comment 307 | ``` 308 | - a section might not have any "content", only a title: 309 | ``` 310 | [section 1] 311 | ``` 312 | 313 | From there I'm able to introduce a `setting` rule, and define `section` in 314 | terms of `section_name` and `setting`. 315 | 316 | module.exports = grammar({ 317 | name: 'ini', 318 | extras: $ => [ 319 | $.comment, 320 | /\s/, 321 | ], 322 | rules: { 323 | document: $ => repeat( 324 | $.section 325 | ), 326 | 327 | section: $ => { 328 | return seq( 329 | // Section must have a name. 330 | $.section_name, 331 | // Section has zero or more settings (name=value pairs). 332 | repeat($.setting), 333 | ) 334 | }, 335 | section_name: $ => seq( 336 | '[', 337 | /[^\[\]]+/, 338 | ']', 339 | '\n', // Section name must be on its own line. 340 | ), 341 | setting: $ => seq( 342 | $.setting_name, 343 | '=', 344 | $.setting_value, 345 | '\n', // Only one setting per line. 346 | ), 347 | setting_name: $ => /[^=]+/, 348 | setting_value: $ => /.+/, 349 | 350 | comment: $ => token( 351 | seq('#', /.*/), 352 | ), 353 | } 354 | }); 355 | 356 | That passes this updated test: 357 | 358 | ================== 359 | Test 1 360 | ================== 361 | 362 | [a section title] 363 | foo = bar 364 | 365 | --- 366 | 367 | (document 368 | (section 369 | (section_name) 370 | (setting (setting_name) (setting_value)))) 371 | 372 | # Actually use it! 373 | 374 | Now we can actually parse stuff! Such as [this AWS config example](https://docs.aws.amazon.com/credref/latest/refdocs/file-format.html): 375 | 376 | File `test-awsconfig`: 377 | 378 | [default] 379 | region = us-west-2 380 | output = json 381 | 382 | [profile dev-user] 383 | region = us-east-1 384 | output = text 385 | 386 | [profile developers] 387 | role_arn = arn:aws:iam::123456789012:role/developers 388 | source_profile = dev-user 389 | region = us-west-2 390 | output = json 391 | 392 | Parse it: 393 | 394 | $ ./node_modules/.bin/tree-sitter parse test-awsconfig 395 | (document [0, 0] - [13, 0] 396 | (section [0, 0] - [13, 0] 397 | (section_name [0, 0] - [1, 0]) 398 | (setting [1, 0] - [2, 0] 399 | (setting_name [1, 0] - [1, 7]) 400 | (setting_value [1, 8] - [1, 18])) 401 | (setting [2, 0] - [4, 0] 402 | (setting_name [2, 0] - [2, 7]) 403 | (setting_value [2, 8] - [2, 13])) 404 | (setting [4, 0] - [6, 0] 405 | (setting_name [4, 0] - [5, 7]) 406 | (setting_value [5, 8] - [5, 18])) 407 | (setting [6, 0] - [8, 0] 408 | (setting_name [6, 0] - [6, 7]) 409 | (setting_value [6, 8] - [6, 13])) 410 | (setting [8, 0] - [10, 0] 411 | (setting_name [8, 0] - [9, 9]) 412 | (setting_value [9, 10] - [9, 52])) 413 | (setting [10, 0] - [11, 0] 414 | (setting_name [10, 0] - [10, 15]) 415 | (setting_value [10, 16] - [10, 25])) 416 | (setting [11, 0] - [12, 0] 417 | (setting_name [11, 0] - [11, 7]) 418 | (setting_value [11, 8] - [11, 18])) 419 | (setting [12, 0] - [13, 0] 420 | (setting_name [12, 0] - [12, 7]) 421 | (setting_value [12, 8] - [12, 13])))) 422 | 423 | # Error handling 424 | 425 | One of the most valuable features of a parser-generator is its ability to flag 426 | errors and report _where_ the error occurred in the source code. 427 | 428 | An ini file without a section heading is an error. 429 | 430 | foo = bar 431 | 432 | $ ./node_modules/.bin/tree-sitter parse test-awsconfig 433 | (document [0, 0] - [1, 0] 434 | (ERROR [0, 0] - [0, 9] 435 | (ERROR [0, 0] - [0, 3]) 436 | (ERROR [0, 6] - [0, 9]))) 437 | 438 | # Preventing duplicate symbols 439 | 440 | From an [early tree-sitter discussion](https://github.com/tree-sitter/tree-sitter/issues/130#issuecomment-364830185), author Max Brunsfeld: 441 | 442 | > Thus far, I don't really consider "type II correctness" to be a goal for Tree-sitter, because I think it's fundamentally incompatible with one of the most important goals of the project: to be able to parse source code files based solely on their language, without knowing any auxiliary information about the files. 443 | > 444 | > For example, tree-sitter-python should be able to parse any python code you can find, without knowing what python version the code is meant to run on. That means it needs to parse the union of Python 2 and Python 3. 445 | 446 | # Capturing whitespace 447 | 448 | Whitespace will be included in _all_ nodes if you have _any_ `$.word` node that captures leading whitespace. 449 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-sitter-ini", 3 | "version": "1.2.1-dev.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "tree-sitter-ini", 9 | "version": "1.2.1-dev.0", 10 | "hasInstallScript": true, 11 | "license": "MIT", 12 | "dependencies": { 13 | "node-addon-api": "^7.1.0", 14 | "node-gyp-build": "^4.8.0" 15 | }, 16 | "devDependencies": { 17 | "prebuildify": "^6.0.0", 18 | "tree-sitter-cli": "^0.20.7" 19 | }, 20 | "peerDependencies": { 21 | "tree-sitter": "^0.21.0" 22 | }, 23 | "peerDependenciesMeta": { 24 | "tree_sitter": { 25 | "optional": true 26 | } 27 | } 28 | }, 29 | "node_modules/base64-js": { 30 | "version": "1.5.1", 31 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 32 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 33 | "dev": true, 34 | "funding": [ 35 | { 36 | "type": "github", 37 | "url": "https://github.com/sponsors/feross" 38 | }, 39 | { 40 | "type": "patreon", 41 | "url": "https://www.patreon.com/feross" 42 | }, 43 | { 44 | "type": "consulting", 45 | "url": "https://feross.org/support" 46 | } 47 | ], 48 | "license": "MIT" 49 | }, 50 | "node_modules/bl": { 51 | "version": "4.1.0", 52 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 53 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 54 | "dev": true, 55 | "license": "MIT", 56 | "dependencies": { 57 | "buffer": "^5.5.0", 58 | "inherits": "^2.0.4", 59 | "readable-stream": "^3.4.0" 60 | } 61 | }, 62 | "node_modules/buffer": { 63 | "version": "5.7.1", 64 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 65 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 66 | "dev": true, 67 | "funding": [ 68 | { 69 | "type": "github", 70 | "url": "https://github.com/sponsors/feross" 71 | }, 72 | { 73 | "type": "patreon", 74 | "url": "https://www.patreon.com/feross" 75 | }, 76 | { 77 | "type": "consulting", 78 | "url": "https://feross.org/support" 79 | } 80 | ], 81 | "license": "MIT", 82 | "dependencies": { 83 | "base64-js": "^1.3.1", 84 | "ieee754": "^1.1.13" 85 | } 86 | }, 87 | "node_modules/chownr": { 88 | "version": "1.1.4", 89 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 90 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", 91 | "dev": true, 92 | "license": "ISC" 93 | }, 94 | "node_modules/end-of-stream": { 95 | "version": "1.4.4", 96 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 97 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 98 | "dev": true, 99 | "license": "MIT", 100 | "dependencies": { 101 | "once": "^1.4.0" 102 | } 103 | }, 104 | "node_modules/fs-constants": { 105 | "version": "1.0.0", 106 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 107 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", 108 | "dev": true, 109 | "license": "MIT" 110 | }, 111 | "node_modules/ieee754": { 112 | "version": "1.2.1", 113 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 114 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 115 | "dev": true, 116 | "funding": [ 117 | { 118 | "type": "github", 119 | "url": "https://github.com/sponsors/feross" 120 | }, 121 | { 122 | "type": "patreon", 123 | "url": "https://www.patreon.com/feross" 124 | }, 125 | { 126 | "type": "consulting", 127 | "url": "https://feross.org/support" 128 | } 129 | ], 130 | "license": "BSD-3-Clause" 131 | }, 132 | "node_modules/inherits": { 133 | "version": "2.0.4", 134 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 135 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 136 | "dev": true, 137 | "license": "ISC" 138 | }, 139 | "node_modules/minimist": { 140 | "version": "1.2.8", 141 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 142 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 143 | "dev": true, 144 | "license": "MIT", 145 | "funding": { 146 | "url": "https://github.com/sponsors/ljharb" 147 | } 148 | }, 149 | "node_modules/mkdirp-classic": { 150 | "version": "0.5.3", 151 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 152 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", 153 | "dev": true, 154 | "license": "MIT" 155 | }, 156 | "node_modules/node-abi": { 157 | "version": "3.74.0", 158 | "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", 159 | "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", 160 | "dev": true, 161 | "license": "MIT", 162 | "dependencies": { 163 | "semver": "^7.3.5" 164 | }, 165 | "engines": { 166 | "node": ">=10" 167 | } 168 | }, 169 | "node_modules/node-addon-api": { 170 | "version": "7.1.1", 171 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", 172 | "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", 173 | "license": "MIT" 174 | }, 175 | "node_modules/node-gyp-build": { 176 | "version": "4.8.4", 177 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", 178 | "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", 179 | "license": "MIT", 180 | "bin": { 181 | "node-gyp-build": "bin.js", 182 | "node-gyp-build-optional": "optional.js", 183 | "node-gyp-build-test": "build-test.js" 184 | } 185 | }, 186 | "node_modules/npm-run-path": { 187 | "version": "3.1.0", 188 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", 189 | "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", 190 | "dev": true, 191 | "license": "MIT", 192 | "dependencies": { 193 | "path-key": "^3.0.0" 194 | }, 195 | "engines": { 196 | "node": ">=8" 197 | } 198 | }, 199 | "node_modules/once": { 200 | "version": "1.4.0", 201 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 202 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 203 | "dev": true, 204 | "license": "ISC", 205 | "dependencies": { 206 | "wrappy": "1" 207 | } 208 | }, 209 | "node_modules/path-key": { 210 | "version": "3.1.1", 211 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 212 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 213 | "dev": true, 214 | "license": "MIT", 215 | "engines": { 216 | "node": ">=8" 217 | } 218 | }, 219 | "node_modules/prebuildify": { 220 | "version": "6.0.1", 221 | "resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz", 222 | "integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==", 223 | "dev": true, 224 | "license": "MIT", 225 | "dependencies": { 226 | "minimist": "^1.2.5", 227 | "mkdirp-classic": "^0.5.3", 228 | "node-abi": "^3.3.0", 229 | "npm-run-path": "^3.1.0", 230 | "pump": "^3.0.0", 231 | "tar-fs": "^2.1.0" 232 | }, 233 | "bin": { 234 | "prebuildify": "bin.js" 235 | } 236 | }, 237 | "node_modules/pump": { 238 | "version": "3.0.2", 239 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", 240 | "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", 241 | "dev": true, 242 | "license": "MIT", 243 | "dependencies": { 244 | "end-of-stream": "^1.1.0", 245 | "once": "^1.3.1" 246 | } 247 | }, 248 | "node_modules/readable-stream": { 249 | "version": "3.6.2", 250 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 251 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 252 | "dev": true, 253 | "license": "MIT", 254 | "dependencies": { 255 | "inherits": "^2.0.3", 256 | "string_decoder": "^1.1.1", 257 | "util-deprecate": "^1.0.1" 258 | }, 259 | "engines": { 260 | "node": ">= 6" 261 | } 262 | }, 263 | "node_modules/safe-buffer": { 264 | "version": "5.2.1", 265 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 266 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 267 | "dev": true, 268 | "funding": [ 269 | { 270 | "type": "github", 271 | "url": "https://github.com/sponsors/feross" 272 | }, 273 | { 274 | "type": "patreon", 275 | "url": "https://www.patreon.com/feross" 276 | }, 277 | { 278 | "type": "consulting", 279 | "url": "https://feross.org/support" 280 | } 281 | ], 282 | "license": "MIT" 283 | }, 284 | "node_modules/semver": { 285 | "version": "7.7.1", 286 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", 287 | "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", 288 | "dev": true, 289 | "license": "ISC", 290 | "bin": { 291 | "semver": "bin/semver.js" 292 | }, 293 | "engines": { 294 | "node": ">=10" 295 | } 296 | }, 297 | "node_modules/string_decoder": { 298 | "version": "1.3.0", 299 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 300 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 301 | "dev": true, 302 | "license": "MIT", 303 | "dependencies": { 304 | "safe-buffer": "~5.2.0" 305 | } 306 | }, 307 | "node_modules/tar-fs": { 308 | "version": "2.1.2", 309 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", 310 | "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", 311 | "dev": true, 312 | "license": "MIT", 313 | "dependencies": { 314 | "chownr": "^1.1.1", 315 | "mkdirp-classic": "^0.5.2", 316 | "pump": "^3.0.0", 317 | "tar-stream": "^2.1.4" 318 | } 319 | }, 320 | "node_modules/tar-stream": { 321 | "version": "2.2.0", 322 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 323 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 324 | "dev": true, 325 | "license": "MIT", 326 | "dependencies": { 327 | "bl": "^4.0.3", 328 | "end-of-stream": "^1.4.1", 329 | "fs-constants": "^1.0.0", 330 | "inherits": "^2.0.3", 331 | "readable-stream": "^3.1.1" 332 | }, 333 | "engines": { 334 | "node": ">=6" 335 | } 336 | }, 337 | "node_modules/tree-sitter": { 338 | "version": "0.21.1", 339 | "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", 340 | "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", 341 | "hasInstallScript": true, 342 | "license": "MIT", 343 | "peer": true, 344 | "dependencies": { 345 | "node-addon-api": "^8.0.0", 346 | "node-gyp-build": "^4.8.0" 347 | } 348 | }, 349 | "node_modules/tree-sitter-cli": { 350 | "version": "0.20.8", 351 | "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.20.8.tgz", 352 | "integrity": "sha512-XjTcS3wdTy/2cc/ptMLc/WRyOLECRYcMTrSWyhZnj1oGSOWbHLTklgsgRICU3cPfb0vy+oZCC33M43u6R1HSCA==", 353 | "dev": true, 354 | "hasInstallScript": true, 355 | "license": "MIT", 356 | "bin": { 357 | "tree-sitter": "cli.js" 358 | } 359 | }, 360 | "node_modules/tree-sitter/node_modules/node-addon-api": { 361 | "version": "8.3.1", 362 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", 363 | "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", 364 | "license": "MIT", 365 | "peer": true, 366 | "engines": { 367 | "node": "^18 || ^20 || >= 21" 368 | } 369 | }, 370 | "node_modules/util-deprecate": { 371 | "version": "1.0.2", 372 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 373 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 374 | "dev": true, 375 | "license": "MIT" 376 | }, 377 | "node_modules/wrappy": { 378 | "version": "1.0.2", 379 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 380 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 381 | "dev": true, 382 | "license": "ISC" 383 | } 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-sitter-ini", 3 | "version": "1.2.1-dev.0", 4 | "description": "INI grammar for tree-sitter", 5 | "main": "bindings/node", 6 | "types": "bindings/node", 7 | "scripts": { 8 | "install": "node-gyp-build", 9 | "prestart": "tree-sitter build --wasm", 10 | "start": "tree-sitter playground", 11 | "test": "node --test bindings/node/*_test.js" 12 | }, 13 | "keywords": [ 14 | "tree-sitter", 15 | "parser", 16 | "ini" 17 | ], 18 | "files": [ 19 | "grammar.js", 20 | "binding.gyp", 21 | "bindings/node/*", 22 | "queries/**", 23 | "src/**" 24 | ], 25 | "author": "Justin M. Keyes", 26 | "license": "MIT", 27 | "dependencies": { 28 | "node-addon-api": "^7.1.0", 29 | "node-gyp-build": "^4.8.0" 30 | }, 31 | "peerDependencies": { 32 | "tree-sitter": "^0.21.0" 33 | }, 34 | "peerDependenciesMeta": { 35 | "tree_sitter": { 36 | "optional": true 37 | } 38 | }, 39 | "devDependencies": { 40 | "tree-sitter-cli": "^0.20.7", 41 | "prebuildify": "^6.0.0" 42 | }, 43 | "tree-sitter": [ 44 | { 45 | "scope": "source.ini", 46 | "highlights": "queries/highlights.scm", 47 | "file-types": [ 48 | "ini" 49 | ] 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "tree-sitter-ini" 7 | description = "Ini grammar for tree-sitter" 8 | version = "0.0.1" 9 | keywords = ["incremental", "parsing", "tree-sitter", "ini"] 10 | classifiers = [ 11 | "Intended Audience :: Developers", 12 | "License :: OSI Approved :: MIT License", 13 | "Topic :: Software Development :: Compilers", 14 | "Topic :: Text Processing :: Linguistic", 15 | "Typing :: Typed" 16 | ] 17 | requires-python = ">=3.8" 18 | license.text = "MIT" 19 | readme = "README.md" 20 | 21 | [project.urls] 22 | Homepage = "https://github.com/tree-sitter/tree-sitter-ini" 23 | 24 | [project.optional-dependencies] 25 | core = ["tree-sitter~=0.21"] 26 | 27 | [tool.cibuildwheel] 28 | build = "cp38-*" 29 | build-frontend = "build" 30 | -------------------------------------------------------------------------------- /queries/folds.scm: -------------------------------------------------------------------------------- 1 | (section) @fold 2 | -------------------------------------------------------------------------------- /queries/highlights.scm: -------------------------------------------------------------------------------- 1 | (section_name 2 | (text) @type) ; consistency with toml 3 | 4 | (comment) @comment @spell 5 | 6 | [ 7 | "[" 8 | "]" 9 | ] @punctuation.bracket 10 | 11 | "=" @operator 12 | 13 | (setting 14 | (setting_name) @property) 15 | 16 | ; (setting_value) @none ; grammar does not support subtypes 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import isdir, join 2 | from platform import system 3 | 4 | from setuptools import Extension, find_packages, setup 5 | from setuptools.command.build import build 6 | from wheel.bdist_wheel import bdist_wheel 7 | 8 | 9 | class Build(build): 10 | def run(self): 11 | if isdir("queries"): 12 | dest = join(self.build_lib, "tree_sitter_ini", "queries") 13 | self.copy_tree("queries", dest) 14 | super().run() 15 | 16 | 17 | class BdistWheel(bdist_wheel): 18 | def get_tag(self): 19 | python, abi, platform = super().get_tag() 20 | if python.startswith("cp"): 21 | python, abi = "cp38", "abi3" 22 | return python, abi, platform 23 | 24 | 25 | setup( 26 | packages=find_packages("bindings/python"), 27 | package_dir={"": "bindings/python"}, 28 | package_data={ 29 | "tree_sitter_ini": ["*.pyi", "py.typed"], 30 | "tree_sitter_ini.queries": ["*.scm"], 31 | }, 32 | ext_package="tree_sitter_ini", 33 | ext_modules=[ 34 | Extension( 35 | name="_binding", 36 | sources=[ 37 | "bindings/python/tree_sitter_ini/binding.c", 38 | "src/parser.c", 39 | # NOTE: if your language uses an external scanner, add it here. 40 | ], 41 | extra_compile_args=[ 42 | "-std=c11", 43 | ] if system() != "Windows" else [ 44 | "/std:c11", 45 | "/utf-8", 46 | ], 47 | define_macros=[ 48 | ("Py_LIMITED_API", "0x03080000"), 49 | ("PY_SSIZE_T_CLEAN", None) 50 | ], 51 | include_dirs=["src"], 52 | py_limited_api=True, 53 | ) 54 | ], 55 | cmdclass={ 56 | "build": Build, 57 | "bdist_wheel": BdistWheel 58 | }, 59 | zip_safe=False 60 | ) 61 | -------------------------------------------------------------------------------- /src/grammar.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://tree-sitter.github.io/tree-sitter/assets/schemas/grammar.schema.json", 3 | "name": "ini", 4 | "rules": { 5 | "document": { 6 | "type": "SEQ", 7 | "members": [ 8 | { 9 | "type": "REPEAT", 10 | "content": { 11 | "type": "SYMBOL", 12 | "name": "_blank" 13 | } 14 | }, 15 | { 16 | "type": "CHOICE", 17 | "members": [ 18 | { 19 | "type": "REPEAT", 20 | "content": { 21 | "type": "SEQ", 22 | "members": [ 23 | { 24 | "type": "SYMBOL", 25 | "name": "setting" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "type": "BLANK" 32 | } 33 | ] 34 | }, 35 | { 36 | "type": "REPEAT", 37 | "content": { 38 | "type": "SYMBOL", 39 | "name": "section" 40 | } 41 | } 42 | ] 43 | }, 44 | "section": { 45 | "type": "PREC_LEFT", 46 | "value": 0, 47 | "content": { 48 | "type": "SEQ", 49 | "members": [ 50 | { 51 | "type": "SYMBOL", 52 | "name": "section_name" 53 | }, 54 | { 55 | "type": "REPEAT", 56 | "content": { 57 | "type": "SEQ", 58 | "members": [ 59 | { 60 | "type": "SYMBOL", 61 | "name": "setting" 62 | } 63 | ] 64 | } 65 | } 66 | ] 67 | } 68 | }, 69 | "section_name": { 70 | "type": "SEQ", 71 | "members": [ 72 | { 73 | "type": "STRING", 74 | "value": "[" 75 | }, 76 | { 77 | "type": "ALIAS", 78 | "content": { 79 | "type": "PATTERN", 80 | "value": "[^\\[\\]]+" 81 | }, 82 | "named": true, 83 | "value": "text" 84 | }, 85 | { 86 | "type": "STRING", 87 | "value": "]" 88 | }, 89 | { 90 | "type": "PATTERN", 91 | "value": "\\r?\\n" 92 | } 93 | ] 94 | }, 95 | "setting": { 96 | "type": "SEQ", 97 | "members": [ 98 | { 99 | "type": "ALIAS", 100 | "content": { 101 | "type": "PATTERN", 102 | "value": "[^;#=\\s\\[]+( *[^;#=\\s\\[])*" 103 | }, 104 | "named": true, 105 | "value": "setting_name" 106 | }, 107 | { 108 | "type": "STRING", 109 | "value": "=" 110 | }, 111 | { 112 | "type": "CHOICE", 113 | "members": [ 114 | { 115 | "type": "ALIAS", 116 | "content": { 117 | "type": "PATTERN", 118 | "value": ".+" 119 | }, 120 | "named": true, 121 | "value": "setting_value" 122 | }, 123 | { 124 | "type": "BLANK" 125 | } 126 | ] 127 | }, 128 | { 129 | "type": "PATTERN", 130 | "value": "\\r?\\n" 131 | } 132 | ] 133 | }, 134 | "comment": { 135 | "type": "SEQ", 136 | "members": [ 137 | { 138 | "type": "PATTERN", 139 | "value": "[;#]" 140 | }, 141 | { 142 | "type": "ALIAS", 143 | "content": { 144 | "type": "PATTERN", 145 | "value": "[^\\r\\n]*" 146 | }, 147 | "named": true, 148 | "value": "text" 149 | }, 150 | { 151 | "type": "PATTERN", 152 | "value": "\\r?\\n" 153 | } 154 | ] 155 | }, 156 | "_blank": { 157 | "type": "FIELD", 158 | "name": "blank", 159 | "content": { 160 | "type": "PATTERN", 161 | "value": "\\r?\\n" 162 | } 163 | } 164 | }, 165 | "extras": [ 166 | { 167 | "type": "SYMBOL", 168 | "name": "comment" 169 | }, 170 | { 171 | "type": "SYMBOL", 172 | "name": "_blank" 173 | }, 174 | { 175 | "type": "PATTERN", 176 | "value": "[\\t ]" 177 | } 178 | ], 179 | "conflicts": [], 180 | "precedences": [], 181 | "externals": [], 182 | "inline": [], 183 | "supertypes": [], 184 | "reserved": {} 185 | } -------------------------------------------------------------------------------- /src/node-types.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "comment", 4 | "named": true, 5 | "fields": {}, 6 | "children": { 7 | "multiple": false, 8 | "required": true, 9 | "types": [ 10 | { 11 | "type": "text", 12 | "named": true 13 | } 14 | ] 15 | } 16 | }, 17 | { 18 | "type": "document", 19 | "named": true, 20 | "root": true, 21 | "fields": {}, 22 | "children": { 23 | "multiple": true, 24 | "required": false, 25 | "types": [ 26 | { 27 | "type": "section", 28 | "named": true 29 | }, 30 | { 31 | "type": "setting", 32 | "named": true 33 | } 34 | ] 35 | } 36 | }, 37 | { 38 | "type": "section", 39 | "named": true, 40 | "fields": {}, 41 | "children": { 42 | "multiple": true, 43 | "required": true, 44 | "types": [ 45 | { 46 | "type": "section_name", 47 | "named": true 48 | }, 49 | { 50 | "type": "setting", 51 | "named": true 52 | } 53 | ] 54 | } 55 | }, 56 | { 57 | "type": "section_name", 58 | "named": true, 59 | "fields": {}, 60 | "children": { 61 | "multiple": false, 62 | "required": true, 63 | "types": [ 64 | { 65 | "type": "text", 66 | "named": true 67 | } 68 | ] 69 | } 70 | }, 71 | { 72 | "type": "setting", 73 | "named": true, 74 | "fields": {}, 75 | "children": { 76 | "multiple": true, 77 | "required": true, 78 | "types": [ 79 | { 80 | "type": "setting_name", 81 | "named": true 82 | }, 83 | { 84 | "type": "setting_value", 85 | "named": true 86 | } 87 | ] 88 | } 89 | }, 90 | { 91 | "type": "=", 92 | "named": false 93 | }, 94 | { 95 | "type": "[", 96 | "named": false 97 | }, 98 | { 99 | "type": "]", 100 | "named": false 101 | }, 102 | { 103 | "type": "setting_name", 104 | "named": true 105 | }, 106 | { 107 | "type": "setting_value", 108 | "named": true 109 | }, 110 | { 111 | "type": "text", 112 | "named": true 113 | } 114 | ] -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | /* Automatically generated by tree-sitter v0.25.2 (6e0618704ad758ba2ea5822faa80bcd36fbeba3d) */ 2 | 3 | #include "tree_sitter/parser.h" 4 | 5 | #if defined(__GNUC__) || defined(__clang__) 6 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 7 | #endif 8 | 9 | #define LANGUAGE_VERSION 14 10 | #define STATE_COUNT 30 11 | #define LARGE_STATE_COUNT 5 12 | #define SYMBOL_COUNT 19 13 | #define ALIAS_COUNT 0 14 | #define TOKEN_COUNT 10 15 | #define EXTERNAL_TOKEN_COUNT 0 16 | #define FIELD_COUNT 1 17 | #define MAX_ALIAS_SEQUENCE_LENGTH 4 18 | #define MAX_RESERVED_WORD_SET_SIZE 0 19 | #define PRODUCTION_ID_COUNT 2 20 | #define SUPERTYPE_COUNT 0 21 | 22 | enum ts_symbol_identifiers { 23 | anon_sym_LBRACK = 1, 24 | aux_sym_section_name_token1 = 2, 25 | anon_sym_RBRACK = 3, 26 | aux_sym_section_name_token2 = 4, 27 | aux_sym_setting_token1 = 5, 28 | anon_sym_EQ = 6, 29 | aux_sym_setting_token2 = 7, 30 | aux_sym_comment_token1 = 8, 31 | aux_sym_comment_token2 = 9, 32 | sym_document = 10, 33 | sym_section = 11, 34 | sym_section_name = 12, 35 | sym_setting = 13, 36 | sym_comment = 14, 37 | sym__blank = 15, 38 | aux_sym_document_repeat1 = 16, 39 | aux_sym_document_repeat2 = 17, 40 | aux_sym_document_repeat3 = 18, 41 | }; 42 | 43 | static const char * const ts_symbol_names[] = { 44 | [ts_builtin_sym_end] = "end", 45 | [anon_sym_LBRACK] = "[", 46 | [aux_sym_section_name_token1] = "text", 47 | [anon_sym_RBRACK] = "]", 48 | [aux_sym_section_name_token2] = "section_name_token2", 49 | [aux_sym_setting_token1] = "setting_name", 50 | [anon_sym_EQ] = "=", 51 | [aux_sym_setting_token2] = "setting_value", 52 | [aux_sym_comment_token1] = "comment_token1", 53 | [aux_sym_comment_token2] = "text", 54 | [sym_document] = "document", 55 | [sym_section] = "section", 56 | [sym_section_name] = "section_name", 57 | [sym_setting] = "setting", 58 | [sym_comment] = "comment", 59 | [sym__blank] = "_blank", 60 | [aux_sym_document_repeat1] = "document_repeat1", 61 | [aux_sym_document_repeat2] = "document_repeat2", 62 | [aux_sym_document_repeat3] = "document_repeat3", 63 | }; 64 | 65 | static const TSSymbol ts_symbol_map[] = { 66 | [ts_builtin_sym_end] = ts_builtin_sym_end, 67 | [anon_sym_LBRACK] = anon_sym_LBRACK, 68 | [aux_sym_section_name_token1] = aux_sym_section_name_token1, 69 | [anon_sym_RBRACK] = anon_sym_RBRACK, 70 | [aux_sym_section_name_token2] = aux_sym_section_name_token2, 71 | [aux_sym_setting_token1] = aux_sym_setting_token1, 72 | [anon_sym_EQ] = anon_sym_EQ, 73 | [aux_sym_setting_token2] = aux_sym_setting_token2, 74 | [aux_sym_comment_token1] = aux_sym_comment_token1, 75 | [aux_sym_comment_token2] = aux_sym_section_name_token1, 76 | [sym_document] = sym_document, 77 | [sym_section] = sym_section, 78 | [sym_section_name] = sym_section_name, 79 | [sym_setting] = sym_setting, 80 | [sym_comment] = sym_comment, 81 | [sym__blank] = sym__blank, 82 | [aux_sym_document_repeat1] = aux_sym_document_repeat1, 83 | [aux_sym_document_repeat2] = aux_sym_document_repeat2, 84 | [aux_sym_document_repeat3] = aux_sym_document_repeat3, 85 | }; 86 | 87 | static const TSSymbolMetadata ts_symbol_metadata[] = { 88 | [ts_builtin_sym_end] = { 89 | .visible = false, 90 | .named = true, 91 | }, 92 | [anon_sym_LBRACK] = { 93 | .visible = true, 94 | .named = false, 95 | }, 96 | [aux_sym_section_name_token1] = { 97 | .visible = true, 98 | .named = true, 99 | }, 100 | [anon_sym_RBRACK] = { 101 | .visible = true, 102 | .named = false, 103 | }, 104 | [aux_sym_section_name_token2] = { 105 | .visible = false, 106 | .named = false, 107 | }, 108 | [aux_sym_setting_token1] = { 109 | .visible = true, 110 | .named = true, 111 | }, 112 | [anon_sym_EQ] = { 113 | .visible = true, 114 | .named = false, 115 | }, 116 | [aux_sym_setting_token2] = { 117 | .visible = true, 118 | .named = true, 119 | }, 120 | [aux_sym_comment_token1] = { 121 | .visible = false, 122 | .named = false, 123 | }, 124 | [aux_sym_comment_token2] = { 125 | .visible = true, 126 | .named = true, 127 | }, 128 | [sym_document] = { 129 | .visible = true, 130 | .named = true, 131 | }, 132 | [sym_section] = { 133 | .visible = true, 134 | .named = true, 135 | }, 136 | [sym_section_name] = { 137 | .visible = true, 138 | .named = true, 139 | }, 140 | [sym_setting] = { 141 | .visible = true, 142 | .named = true, 143 | }, 144 | [sym_comment] = { 145 | .visible = true, 146 | .named = true, 147 | }, 148 | [sym__blank] = { 149 | .visible = false, 150 | .named = true, 151 | }, 152 | [aux_sym_document_repeat1] = { 153 | .visible = false, 154 | .named = false, 155 | }, 156 | [aux_sym_document_repeat2] = { 157 | .visible = false, 158 | .named = false, 159 | }, 160 | [aux_sym_document_repeat3] = { 161 | .visible = false, 162 | .named = false, 163 | }, 164 | }; 165 | 166 | enum ts_field_identifiers { 167 | field_blank = 1, 168 | }; 169 | 170 | static const char * const ts_field_names[] = { 171 | [0] = NULL, 172 | [field_blank] = "blank", 173 | }; 174 | 175 | static const TSMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = { 176 | [1] = {.index = 0, .length = 1}, 177 | }; 178 | 179 | static const TSFieldMapEntry ts_field_map_entries[] = { 180 | [0] = 181 | {field_blank, 0}, 182 | }; 183 | 184 | static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = { 185 | [0] = {0}, 186 | }; 187 | 188 | static const uint16_t ts_non_terminal_alias_map[] = { 189 | 0, 190 | }; 191 | 192 | static const TSStateId ts_primary_state_ids[STATE_COUNT] = { 193 | [0] = 0, 194 | [1] = 1, 195 | [2] = 2, 196 | [3] = 3, 197 | [4] = 4, 198 | [5] = 5, 199 | [6] = 6, 200 | [7] = 7, 201 | [8] = 8, 202 | [9] = 9, 203 | [10] = 10, 204 | [11] = 11, 205 | [12] = 12, 206 | [13] = 13, 207 | [14] = 14, 208 | [15] = 15, 209 | [16] = 16, 210 | [17] = 17, 211 | [18] = 18, 212 | [19] = 19, 213 | [20] = 20, 214 | [21] = 21, 215 | [22] = 22, 216 | [23] = 23, 217 | [24] = 24, 218 | [25] = 25, 219 | [26] = 26, 220 | [27] = 27, 221 | [28] = 17, 222 | [29] = 29, 223 | }; 224 | 225 | static bool ts_lex(TSLexer *lexer, TSStateId state) { 226 | START_LEXER(); 227 | eof = lexer->eof(lexer); 228 | switch (state) { 229 | case 0: 230 | if (eof) ADVANCE(6); 231 | if (lookahead == '\n') ADVANCE(12); 232 | if (lookahead == '\r') ADVANCE(1); 233 | if (lookahead == '=') ADVANCE(14); 234 | if (lookahead == '[') ADVANCE(7); 235 | if (lookahead == ']') ADVANCE(11); 236 | if (lookahead == '\t' || 237 | lookahead == ' ') SKIP(0); 238 | if (lookahead == '#' || 239 | lookahead == ';') ADVANCE(18); 240 | END_STATE(); 241 | case 1: 242 | if (lookahead == '\n') ADVANCE(12); 243 | END_STATE(); 244 | case 2: 245 | if (lookahead == '\n') ADVANCE(12); 246 | if (lookahead == '\r') ADVANCE(15); 247 | if (lookahead == '\t' || 248 | lookahead == ' ') ADVANCE(16); 249 | if (lookahead == '#' || 250 | lookahead == ';') ADVANCE(17); 251 | if (lookahead != 0) ADVANCE(17); 252 | END_STATE(); 253 | case 3: 254 | if (lookahead == '\n') ADVANCE(10); 255 | if (lookahead == '\r') ADVANCE(9); 256 | if (lookahead == '\t' || 257 | lookahead == ' ') ADVANCE(8); 258 | if (lookahead == '#' || 259 | lookahead == ';') ADVANCE(10); 260 | if (lookahead != 0 && 261 | lookahead != '[' && 262 | lookahead != ']') ADVANCE(10); 263 | END_STATE(); 264 | case 4: 265 | if (lookahead == ' ') ADVANCE(4); 266 | if (lookahead != 0 && 267 | (lookahead < '\t' || '\r' < lookahead) && 268 | lookahead != '#' && 269 | lookahead != ';' && 270 | lookahead != '=' && 271 | lookahead != '[') ADVANCE(13); 272 | END_STATE(); 273 | case 5: 274 | if (eof) ADVANCE(6); 275 | if (lookahead == '\n') ADVANCE(12); 276 | if (lookahead == '\r') ADVANCE(1); 277 | if (lookahead == '[') ADVANCE(7); 278 | if (lookahead == '\t' || 279 | lookahead == ' ') SKIP(5); 280 | if (lookahead == '#' || 281 | lookahead == ';') ADVANCE(18); 282 | if (lookahead != 0 && 283 | (lookahead < '\t' || '\r' < lookahead) && 284 | lookahead != '=') ADVANCE(13); 285 | END_STATE(); 286 | case 6: 287 | ACCEPT_TOKEN(ts_builtin_sym_end); 288 | END_STATE(); 289 | case 7: 290 | ACCEPT_TOKEN(anon_sym_LBRACK); 291 | END_STATE(); 292 | case 8: 293 | ACCEPT_TOKEN(aux_sym_section_name_token1); 294 | if (lookahead == '\n') ADVANCE(10); 295 | if (lookahead == '\r') ADVANCE(9); 296 | if (lookahead == '\t' || 297 | lookahead == ' ') ADVANCE(8); 298 | if (lookahead == '#' || 299 | lookahead == ';') ADVANCE(10); 300 | if (lookahead != 0 && 301 | lookahead != '[' && 302 | lookahead != ']') ADVANCE(10); 303 | END_STATE(); 304 | case 9: 305 | ACCEPT_TOKEN(aux_sym_section_name_token1); 306 | if (lookahead == '\n') ADVANCE(10); 307 | if (lookahead != 0 && 308 | lookahead != '[' && 309 | lookahead != ']') ADVANCE(10); 310 | END_STATE(); 311 | case 10: 312 | ACCEPT_TOKEN(aux_sym_section_name_token1); 313 | if (lookahead != 0 && 314 | lookahead != '[' && 315 | lookahead != ']') ADVANCE(10); 316 | END_STATE(); 317 | case 11: 318 | ACCEPT_TOKEN(anon_sym_RBRACK); 319 | END_STATE(); 320 | case 12: 321 | ACCEPT_TOKEN(aux_sym_section_name_token2); 322 | END_STATE(); 323 | case 13: 324 | ACCEPT_TOKEN(aux_sym_setting_token1); 325 | if (lookahead == ' ') ADVANCE(4); 326 | if (lookahead != 0 && 327 | (lookahead < '\t' || '\r' < lookahead) && 328 | lookahead != '#' && 329 | lookahead != ';' && 330 | lookahead != '=' && 331 | lookahead != '[') ADVANCE(13); 332 | END_STATE(); 333 | case 14: 334 | ACCEPT_TOKEN(anon_sym_EQ); 335 | END_STATE(); 336 | case 15: 337 | ACCEPT_TOKEN(aux_sym_setting_token2); 338 | if (lookahead == '\n') ADVANCE(12); 339 | if (lookahead != 0) ADVANCE(17); 340 | END_STATE(); 341 | case 16: 342 | ACCEPT_TOKEN(aux_sym_setting_token2); 343 | if (lookahead == '\r') ADVANCE(15); 344 | if (lookahead == '\t' || 345 | lookahead == ' ') ADVANCE(16); 346 | if (lookahead == '#' || 347 | lookahead == ';') ADVANCE(17); 348 | if (lookahead != 0 && 349 | lookahead != '\t' && 350 | lookahead != '\n') ADVANCE(17); 351 | END_STATE(); 352 | case 17: 353 | ACCEPT_TOKEN(aux_sym_setting_token2); 354 | if (lookahead != 0 && 355 | lookahead != '\n') ADVANCE(17); 356 | END_STATE(); 357 | case 18: 358 | ACCEPT_TOKEN(aux_sym_comment_token1); 359 | END_STATE(); 360 | case 19: 361 | ACCEPT_TOKEN(aux_sym_comment_token1); 362 | if (lookahead != 0 && 363 | lookahead != '\n' && 364 | lookahead != '\r') ADVANCE(21); 365 | END_STATE(); 366 | case 20: 367 | ACCEPT_TOKEN(aux_sym_comment_token2); 368 | if (lookahead == '\t' || 369 | lookahead == ' ') ADVANCE(20); 370 | if (lookahead == '#' || 371 | lookahead == ';') ADVANCE(19); 372 | if (lookahead != 0 && 373 | lookahead != '\t' && 374 | lookahead != '\n' && 375 | lookahead != '\r') ADVANCE(21); 376 | END_STATE(); 377 | case 21: 378 | ACCEPT_TOKEN(aux_sym_comment_token2); 379 | if (lookahead != 0 && 380 | lookahead != '\n' && 381 | lookahead != '\r') ADVANCE(21); 382 | END_STATE(); 383 | default: 384 | return false; 385 | } 386 | } 387 | 388 | static const TSLexMode ts_lex_modes[STATE_COUNT] = { 389 | [0] = {.lex_state = 0}, 390 | [1] = {.lex_state = 5}, 391 | [2] = {.lex_state = 5}, 392 | [3] = {.lex_state = 5}, 393 | [4] = {.lex_state = 5}, 394 | [5] = {.lex_state = 5}, 395 | [6] = {.lex_state = 0}, 396 | [7] = {.lex_state = 0}, 397 | [8] = {.lex_state = 5}, 398 | [9] = {.lex_state = 0}, 399 | [10] = {.lex_state = 5}, 400 | [11] = {.lex_state = 0}, 401 | [12] = {.lex_state = 5}, 402 | [13] = {.lex_state = 5}, 403 | [14] = {.lex_state = 5}, 404 | [15] = {.lex_state = 5}, 405 | [16] = {.lex_state = 5}, 406 | [17] = {.lex_state = 5}, 407 | [18] = {.lex_state = 0}, 408 | [19] = {.lex_state = 3}, 409 | [20] = {.lex_state = 0}, 410 | [21] = {.lex_state = 0}, 411 | [22] = {.lex_state = 20}, 412 | [23] = {.lex_state = 0}, 413 | [24] = {.lex_state = 2}, 414 | [25] = {.lex_state = 0}, 415 | [26] = {.lex_state = 0}, 416 | [27] = {.lex_state = 0}, 417 | [28] = {(TSStateId)(-1),}, 418 | [29] = {(TSStateId)(-1),}, 419 | }; 420 | 421 | static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = { 422 | [STATE(0)] = { 423 | [sym_comment] = STATE(0), 424 | [sym__blank] = STATE(0), 425 | [ts_builtin_sym_end] = ACTIONS(1), 426 | [anon_sym_LBRACK] = ACTIONS(1), 427 | [anon_sym_RBRACK] = ACTIONS(1), 428 | [aux_sym_section_name_token2] = ACTIONS(3), 429 | [anon_sym_EQ] = ACTIONS(1), 430 | [aux_sym_comment_token1] = ACTIONS(5), 431 | }, 432 | [STATE(1)] = { 433 | [sym_document] = STATE(20), 434 | [sym_section] = STATE(18), 435 | [sym_section_name] = STATE(5), 436 | [sym_setting] = STATE(13), 437 | [sym_comment] = STATE(1), 438 | [sym__blank] = STATE(1), 439 | [aux_sym_document_repeat1] = STATE(2), 440 | [aux_sym_document_repeat2] = STATE(3), 441 | [aux_sym_document_repeat3] = STATE(6), 442 | [ts_builtin_sym_end] = ACTIONS(7), 443 | [anon_sym_LBRACK] = ACTIONS(9), 444 | [aux_sym_section_name_token2] = ACTIONS(11), 445 | [aux_sym_setting_token1] = ACTIONS(13), 446 | [aux_sym_comment_token1] = ACTIONS(5), 447 | }, 448 | [STATE(2)] = { 449 | [sym_section] = STATE(18), 450 | [sym_section_name] = STATE(5), 451 | [sym_setting] = STATE(13), 452 | [sym_comment] = STATE(2), 453 | [sym__blank] = STATE(2), 454 | [aux_sym_document_repeat1] = STATE(12), 455 | [aux_sym_document_repeat2] = STATE(4), 456 | [aux_sym_document_repeat3] = STATE(7), 457 | [ts_builtin_sym_end] = ACTIONS(15), 458 | [anon_sym_LBRACK] = ACTIONS(9), 459 | [aux_sym_section_name_token2] = ACTIONS(11), 460 | [aux_sym_setting_token1] = ACTIONS(13), 461 | [aux_sym_comment_token1] = ACTIONS(5), 462 | }, 463 | [STATE(3)] = { 464 | [sym_section] = STATE(18), 465 | [sym_section_name] = STATE(5), 466 | [sym_setting] = STATE(13), 467 | [sym_comment] = STATE(3), 468 | [sym__blank] = STATE(3), 469 | [aux_sym_document_repeat2] = STATE(10), 470 | [aux_sym_document_repeat3] = STATE(7), 471 | [ts_builtin_sym_end] = ACTIONS(15), 472 | [anon_sym_LBRACK] = ACTIONS(9), 473 | [aux_sym_section_name_token2] = ACTIONS(3), 474 | [aux_sym_setting_token1] = ACTIONS(13), 475 | [aux_sym_comment_token1] = ACTIONS(5), 476 | }, 477 | [STATE(4)] = { 478 | [sym_section] = STATE(18), 479 | [sym_section_name] = STATE(5), 480 | [sym_setting] = STATE(13), 481 | [sym_comment] = STATE(4), 482 | [sym__blank] = STATE(4), 483 | [aux_sym_document_repeat2] = STATE(10), 484 | [aux_sym_document_repeat3] = STATE(11), 485 | [ts_builtin_sym_end] = ACTIONS(17), 486 | [anon_sym_LBRACK] = ACTIONS(9), 487 | [aux_sym_section_name_token2] = ACTIONS(3), 488 | [aux_sym_setting_token1] = ACTIONS(13), 489 | [aux_sym_comment_token1] = ACTIONS(5), 490 | }, 491 | }; 492 | 493 | static const uint16_t ts_small_parse_table[] = { 494 | [0] = 7, 495 | ACTIONS(3), 1, 496 | aux_sym_section_name_token2, 497 | ACTIONS(5), 1, 498 | aux_sym_comment_token1, 499 | ACTIONS(13), 1, 500 | aux_sym_setting_token1, 501 | STATE(8), 1, 502 | aux_sym_document_repeat2, 503 | STATE(13), 1, 504 | sym_setting, 505 | ACTIONS(19), 2, 506 | ts_builtin_sym_end, 507 | anon_sym_LBRACK, 508 | STATE(5), 2, 509 | sym_comment, 510 | sym__blank, 511 | [24] = 8, 512 | ACTIONS(3), 1, 513 | aux_sym_section_name_token2, 514 | ACTIONS(5), 1, 515 | aux_sym_comment_token1, 516 | ACTIONS(9), 1, 517 | anon_sym_LBRACK, 518 | ACTIONS(15), 1, 519 | ts_builtin_sym_end, 520 | STATE(5), 1, 521 | sym_section_name, 522 | STATE(9), 1, 523 | aux_sym_document_repeat3, 524 | STATE(18), 1, 525 | sym_section, 526 | STATE(6), 2, 527 | sym_comment, 528 | sym__blank, 529 | [50] = 8, 530 | ACTIONS(3), 1, 531 | aux_sym_section_name_token2, 532 | ACTIONS(5), 1, 533 | aux_sym_comment_token1, 534 | ACTIONS(9), 1, 535 | anon_sym_LBRACK, 536 | ACTIONS(17), 1, 537 | ts_builtin_sym_end, 538 | STATE(5), 1, 539 | sym_section_name, 540 | STATE(9), 1, 541 | aux_sym_document_repeat3, 542 | STATE(18), 1, 543 | sym_section, 544 | STATE(7), 2, 545 | sym_comment, 546 | sym__blank, 547 | [76] = 7, 548 | ACTIONS(3), 1, 549 | aux_sym_section_name_token2, 550 | ACTIONS(5), 1, 551 | aux_sym_comment_token1, 552 | ACTIONS(13), 1, 553 | aux_sym_setting_token1, 554 | STATE(10), 1, 555 | aux_sym_document_repeat2, 556 | STATE(13), 1, 557 | sym_setting, 558 | ACTIONS(21), 2, 559 | ts_builtin_sym_end, 560 | anon_sym_LBRACK, 561 | STATE(8), 2, 562 | sym_comment, 563 | sym__blank, 564 | [100] = 7, 565 | ACTIONS(3), 1, 566 | aux_sym_section_name_token2, 567 | ACTIONS(5), 1, 568 | aux_sym_comment_token1, 569 | ACTIONS(23), 1, 570 | ts_builtin_sym_end, 571 | ACTIONS(25), 1, 572 | anon_sym_LBRACK, 573 | STATE(5), 1, 574 | sym_section_name, 575 | STATE(18), 1, 576 | sym_section, 577 | STATE(9), 3, 578 | sym_comment, 579 | sym__blank, 580 | aux_sym_document_repeat3, 581 | [124] = 6, 582 | ACTIONS(3), 1, 583 | aux_sym_section_name_token2, 584 | ACTIONS(5), 1, 585 | aux_sym_comment_token1, 586 | ACTIONS(30), 1, 587 | aux_sym_setting_token1, 588 | STATE(13), 1, 589 | sym_setting, 590 | ACTIONS(28), 2, 591 | ts_builtin_sym_end, 592 | anon_sym_LBRACK, 593 | STATE(10), 3, 594 | sym_comment, 595 | sym__blank, 596 | aux_sym_document_repeat2, 597 | [146] = 8, 598 | ACTIONS(3), 1, 599 | aux_sym_section_name_token2, 600 | ACTIONS(5), 1, 601 | aux_sym_comment_token1, 602 | ACTIONS(9), 1, 603 | anon_sym_LBRACK, 604 | ACTIONS(33), 1, 605 | ts_builtin_sym_end, 606 | STATE(5), 1, 607 | sym_section_name, 608 | STATE(9), 1, 609 | aux_sym_document_repeat3, 610 | STATE(18), 1, 611 | sym_section, 612 | STATE(11), 2, 613 | sym_comment, 614 | sym__blank, 615 | [172] = 4, 616 | ACTIONS(5), 1, 617 | aux_sym_comment_token1, 618 | ACTIONS(37), 1, 619 | aux_sym_section_name_token2, 620 | ACTIONS(35), 3, 621 | ts_builtin_sym_end, 622 | anon_sym_LBRACK, 623 | aux_sym_setting_token1, 624 | STATE(12), 3, 625 | sym_comment, 626 | sym__blank, 627 | aux_sym_document_repeat1, 628 | [189] = 4, 629 | ACTIONS(3), 1, 630 | aux_sym_section_name_token2, 631 | ACTIONS(5), 1, 632 | aux_sym_comment_token1, 633 | STATE(13), 2, 634 | sym_comment, 635 | sym__blank, 636 | ACTIONS(40), 3, 637 | ts_builtin_sym_end, 638 | anon_sym_LBRACK, 639 | aux_sym_setting_token1, 640 | [205] = 4, 641 | ACTIONS(3), 1, 642 | aux_sym_section_name_token2, 643 | ACTIONS(5), 1, 644 | aux_sym_comment_token1, 645 | STATE(14), 2, 646 | sym_comment, 647 | sym__blank, 648 | ACTIONS(42), 3, 649 | ts_builtin_sym_end, 650 | anon_sym_LBRACK, 651 | aux_sym_setting_token1, 652 | [221] = 4, 653 | ACTIONS(3), 1, 654 | aux_sym_section_name_token2, 655 | ACTIONS(5), 1, 656 | aux_sym_comment_token1, 657 | STATE(15), 2, 658 | sym_comment, 659 | sym__blank, 660 | ACTIONS(44), 3, 661 | ts_builtin_sym_end, 662 | anon_sym_LBRACK, 663 | aux_sym_setting_token1, 664 | [237] = 4, 665 | ACTIONS(3), 1, 666 | aux_sym_section_name_token2, 667 | ACTIONS(5), 1, 668 | aux_sym_comment_token1, 669 | STATE(16), 2, 670 | sym_comment, 671 | sym__blank, 672 | ACTIONS(46), 3, 673 | ts_builtin_sym_end, 674 | anon_sym_LBRACK, 675 | aux_sym_setting_token1, 676 | [253] = 3, 677 | ACTIONS(5), 1, 678 | aux_sym_comment_token1, 679 | STATE(17), 2, 680 | sym_comment, 681 | sym__blank, 682 | ACTIONS(48), 4, 683 | ts_builtin_sym_end, 684 | anon_sym_LBRACK, 685 | aux_sym_section_name_token2, 686 | aux_sym_setting_token1, 687 | [267] = 4, 688 | ACTIONS(3), 1, 689 | aux_sym_section_name_token2, 690 | ACTIONS(5), 1, 691 | aux_sym_comment_token1, 692 | ACTIONS(50), 2, 693 | ts_builtin_sym_end, 694 | anon_sym_LBRACK, 695 | STATE(18), 2, 696 | sym_comment, 697 | sym__blank, 698 | [282] = 4, 699 | ACTIONS(52), 1, 700 | aux_sym_section_name_token1, 701 | ACTIONS(54), 1, 702 | aux_sym_section_name_token2, 703 | ACTIONS(56), 1, 704 | aux_sym_comment_token1, 705 | STATE(19), 2, 706 | sym_comment, 707 | sym__blank, 708 | [296] = 4, 709 | ACTIONS(3), 1, 710 | aux_sym_section_name_token2, 711 | ACTIONS(5), 1, 712 | aux_sym_comment_token1, 713 | ACTIONS(58), 1, 714 | ts_builtin_sym_end, 715 | STATE(20), 2, 716 | sym_comment, 717 | sym__blank, 718 | [310] = 4, 719 | ACTIONS(3), 1, 720 | aux_sym_section_name_token2, 721 | ACTIONS(5), 1, 722 | aux_sym_comment_token1, 723 | ACTIONS(60), 1, 724 | anon_sym_EQ, 725 | STATE(21), 2, 726 | sym_comment, 727 | sym__blank, 728 | [324] = 4, 729 | ACTIONS(54), 1, 730 | aux_sym_section_name_token2, 731 | ACTIONS(56), 1, 732 | aux_sym_comment_token1, 733 | ACTIONS(62), 1, 734 | aux_sym_comment_token2, 735 | STATE(22), 2, 736 | sym_comment, 737 | sym__blank, 738 | [338] = 4, 739 | ACTIONS(3), 1, 740 | aux_sym_section_name_token2, 741 | ACTIONS(5), 1, 742 | aux_sym_comment_token1, 743 | ACTIONS(64), 1, 744 | anon_sym_RBRACK, 745 | STATE(23), 2, 746 | sym_comment, 747 | sym__blank, 748 | [352] = 4, 749 | ACTIONS(56), 1, 750 | aux_sym_comment_token1, 751 | ACTIONS(66), 1, 752 | aux_sym_section_name_token2, 753 | ACTIONS(68), 1, 754 | aux_sym_setting_token2, 755 | STATE(24), 2, 756 | sym_comment, 757 | sym__blank, 758 | [366] = 3, 759 | ACTIONS(5), 1, 760 | aux_sym_comment_token1, 761 | ACTIONS(70), 1, 762 | aux_sym_section_name_token2, 763 | STATE(25), 2, 764 | sym_comment, 765 | sym__blank, 766 | [377] = 3, 767 | ACTIONS(5), 1, 768 | aux_sym_comment_token1, 769 | ACTIONS(72), 1, 770 | aux_sym_section_name_token2, 771 | STATE(26), 2, 772 | sym_comment, 773 | sym__blank, 774 | [388] = 3, 775 | ACTIONS(5), 1, 776 | aux_sym_comment_token1, 777 | ACTIONS(74), 1, 778 | aux_sym_section_name_token2, 779 | STATE(27), 2, 780 | sym_comment, 781 | sym__blank, 782 | [399] = 1, 783 | ACTIONS(48), 1, 784 | ts_builtin_sym_end, 785 | [403] = 1, 786 | ACTIONS(76), 1, 787 | ts_builtin_sym_end, 788 | }; 789 | 790 | static const uint32_t ts_small_parse_table_map[] = { 791 | [SMALL_STATE(5)] = 0, 792 | [SMALL_STATE(6)] = 24, 793 | [SMALL_STATE(7)] = 50, 794 | [SMALL_STATE(8)] = 76, 795 | [SMALL_STATE(9)] = 100, 796 | [SMALL_STATE(10)] = 124, 797 | [SMALL_STATE(11)] = 146, 798 | [SMALL_STATE(12)] = 172, 799 | [SMALL_STATE(13)] = 189, 800 | [SMALL_STATE(14)] = 205, 801 | [SMALL_STATE(15)] = 221, 802 | [SMALL_STATE(16)] = 237, 803 | [SMALL_STATE(17)] = 253, 804 | [SMALL_STATE(18)] = 267, 805 | [SMALL_STATE(19)] = 282, 806 | [SMALL_STATE(20)] = 296, 807 | [SMALL_STATE(21)] = 310, 808 | [SMALL_STATE(22)] = 324, 809 | [SMALL_STATE(23)] = 338, 810 | [SMALL_STATE(24)] = 352, 811 | [SMALL_STATE(25)] = 366, 812 | [SMALL_STATE(26)] = 377, 813 | [SMALL_STATE(27)] = 388, 814 | [SMALL_STATE(28)] = 399, 815 | [SMALL_STATE(29)] = 403, 816 | }; 817 | 818 | static const TSParseActionEntry ts_parse_actions[] = { 819 | [0] = {.entry = {.count = 0, .reusable = false}}, 820 | [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(), 821 | [3] = {.entry = {.count = 1, .reusable = true}}, SHIFT(28), 822 | [5] = {.entry = {.count = 1, .reusable = true}}, SHIFT(22), 823 | [7] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_document, 0, 0, 0), 824 | [9] = {.entry = {.count = 1, .reusable = true}}, SHIFT(19), 825 | [11] = {.entry = {.count = 1, .reusable = true}}, SHIFT(17), 826 | [13] = {.entry = {.count = 1, .reusable = true}}, SHIFT(21), 827 | [15] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_document, 1, 0, 0), 828 | [17] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_document, 2, 0, 0), 829 | [19] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_section, 1, 0, 0), 830 | [21] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_section, 2, 0, 0), 831 | [23] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_document_repeat3, 2, 0, 0), 832 | [25] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_document_repeat3, 2, 0, 0), SHIFT_REPEAT(19), 833 | [28] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_document_repeat2, 2, 0, 0), 834 | [30] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_document_repeat2, 2, 0, 0), SHIFT_REPEAT(21), 835 | [33] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_document, 3, 0, 0), 836 | [35] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_document_repeat1, 2, 0, 0), 837 | [37] = {.entry = {.count = 2, .reusable = true}}, REDUCE(aux_sym_document_repeat1, 2, 0, 0), SHIFT_REPEAT(17), 838 | [40] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_document_repeat2, 1, 0, 0), 839 | [42] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_section_name, 4, 0, 0), 840 | [44] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_setting, 3, 0, 0), 841 | [46] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_setting, 4, 0, 0), 842 | [48] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__blank, 1, 0, 1), 843 | [50] = {.entry = {.count = 1, .reusable = true}}, REDUCE(aux_sym_document_repeat3, 1, 0, 0), 844 | [52] = {.entry = {.count = 1, .reusable = true}}, SHIFT(23), 845 | [54] = {.entry = {.count = 1, .reusable = false}}, SHIFT(28), 846 | [56] = {.entry = {.count = 1, .reusable = false}}, SHIFT(22), 847 | [58] = {.entry = {.count = 1, .reusable = true}}, ACCEPT_INPUT(), 848 | [60] = {.entry = {.count = 1, .reusable = true}}, SHIFT(24), 849 | [62] = {.entry = {.count = 1, .reusable = false}}, SHIFT(25), 850 | [64] = {.entry = {.count = 1, .reusable = true}}, SHIFT(27), 851 | [66] = {.entry = {.count = 1, .reusable = false}}, SHIFT(15), 852 | [68] = {.entry = {.count = 1, .reusable = false}}, SHIFT(26), 853 | [70] = {.entry = {.count = 1, .reusable = true}}, SHIFT(29), 854 | [72] = {.entry = {.count = 1, .reusable = true}}, SHIFT(16), 855 | [74] = {.entry = {.count = 1, .reusable = true}}, SHIFT(14), 856 | [76] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_comment, 3, 0, 0), 857 | }; 858 | 859 | #ifdef __cplusplus 860 | extern "C" { 861 | #endif 862 | #ifdef TREE_SITTER_HIDE_SYMBOLS 863 | #define TS_PUBLIC 864 | #elif defined(_WIN32) 865 | #define TS_PUBLIC __declspec(dllexport) 866 | #else 867 | #define TS_PUBLIC __attribute__((visibility("default"))) 868 | #endif 869 | 870 | TS_PUBLIC const TSLanguage *tree_sitter_ini(void) { 871 | static const TSLanguage language = { 872 | .abi_version = LANGUAGE_VERSION, 873 | .symbol_count = SYMBOL_COUNT, 874 | .alias_count = ALIAS_COUNT, 875 | .token_count = TOKEN_COUNT, 876 | .external_token_count = EXTERNAL_TOKEN_COUNT, 877 | .state_count = STATE_COUNT, 878 | .large_state_count = LARGE_STATE_COUNT, 879 | .production_id_count = PRODUCTION_ID_COUNT, 880 | .field_count = FIELD_COUNT, 881 | .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH, 882 | .parse_table = &ts_parse_table[0][0], 883 | .small_parse_table = ts_small_parse_table, 884 | .small_parse_table_map = ts_small_parse_table_map, 885 | .parse_actions = ts_parse_actions, 886 | .symbol_names = ts_symbol_names, 887 | .field_names = ts_field_names, 888 | .field_map_slices = ts_field_map_slices, 889 | .field_map_entries = ts_field_map_entries, 890 | .symbol_metadata = ts_symbol_metadata, 891 | .public_symbol_map = ts_symbol_map, 892 | .alias_map = ts_non_terminal_alias_map, 893 | .alias_sequences = &ts_alias_sequences[0][0], 894 | .lex_modes = (const void*)ts_lex_modes, 895 | .lex_fn = ts_lex, 896 | .primary_state_ids = ts_primary_state_ids, 897 | }; 898 | return &language; 899 | } 900 | #ifdef __cplusplus 901 | } 902 | #endif 903 | -------------------------------------------------------------------------------- /src/tree_sitter/alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_ALLOC_H_ 2 | #define TREE_SITTER_ALLOC_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // Allow clients to override allocation functions 13 | #ifdef TREE_SITTER_REUSE_ALLOCATOR 14 | 15 | extern void *(*ts_current_malloc)(size_t size); 16 | extern void *(*ts_current_calloc)(size_t count, size_t size); 17 | extern void *(*ts_current_realloc)(void *ptr, size_t size); 18 | extern void (*ts_current_free)(void *ptr); 19 | 20 | #ifndef ts_malloc 21 | #define ts_malloc ts_current_malloc 22 | #endif 23 | #ifndef ts_calloc 24 | #define ts_calloc ts_current_calloc 25 | #endif 26 | #ifndef ts_realloc 27 | #define ts_realloc ts_current_realloc 28 | #endif 29 | #ifndef ts_free 30 | #define ts_free ts_current_free 31 | #endif 32 | 33 | #else 34 | 35 | #ifndef ts_malloc 36 | #define ts_malloc malloc 37 | #endif 38 | #ifndef ts_calloc 39 | #define ts_calloc calloc 40 | #endif 41 | #ifndef ts_realloc 42 | #define ts_realloc realloc 43 | #endif 44 | #ifndef ts_free 45 | #define ts_free free 46 | #endif 47 | 48 | #endif 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif // TREE_SITTER_ALLOC_H_ 55 | -------------------------------------------------------------------------------- /src/tree_sitter/array.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_ARRAY_H_ 2 | #define TREE_SITTER_ARRAY_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "./alloc.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(push) 18 | #pragma warning(disable : 4101) 19 | #elif defined(__GNUC__) || defined(__clang__) 20 | #pragma GCC diagnostic push 21 | #pragma GCC diagnostic ignored "-Wunused-variable" 22 | #endif 23 | 24 | #define Array(T) \ 25 | struct { \ 26 | T *contents; \ 27 | uint32_t size; \ 28 | uint32_t capacity; \ 29 | } 30 | 31 | /// Initialize an array. 32 | #define array_init(self) \ 33 | ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) 34 | 35 | /// Create an empty array. 36 | #define array_new() \ 37 | { NULL, 0, 0 } 38 | 39 | /// Get a pointer to the element at a given `index` in the array. 40 | #define array_get(self, _index) \ 41 | (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index]) 42 | 43 | /// Get a pointer to the first element in the array. 44 | #define array_front(self) array_get(self, 0) 45 | 46 | /// Get a pointer to the last element in the array. 47 | #define array_back(self) array_get(self, (self)->size - 1) 48 | 49 | /// Clear the array, setting its size to zero. Note that this does not free any 50 | /// memory allocated for the array's contents. 51 | #define array_clear(self) ((self)->size = 0) 52 | 53 | /// Reserve `new_capacity` elements of space in the array. If `new_capacity` is 54 | /// less than the array's current capacity, this function has no effect. 55 | #define array_reserve(self, new_capacity) \ 56 | _array__reserve((Array *)(self), array_elem_size(self), new_capacity) 57 | 58 | /// Free any memory allocated for this array. Note that this does not free any 59 | /// memory allocated for the array's contents. 60 | #define array_delete(self) _array__delete((Array *)(self)) 61 | 62 | /// Push a new `element` onto the end of the array. 63 | #define array_push(self, element) \ 64 | (_array__grow((Array *)(self), 1, array_elem_size(self)), \ 65 | (self)->contents[(self)->size++] = (element)) 66 | 67 | /// Increase the array's size by `count` elements. 68 | /// New elements are zero-initialized. 69 | #define array_grow_by(self, count) \ 70 | do { \ 71 | if ((count) == 0) break; \ 72 | _array__grow((Array *)(self), count, array_elem_size(self)); \ 73 | memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \ 74 | (self)->size += (count); \ 75 | } while (0) 76 | 77 | /// Append all elements from one array to the end of another. 78 | #define array_push_all(self, other) \ 79 | array_extend((self), (other)->size, (other)->contents) 80 | 81 | /// Append `count` elements to the end of the array, reading their values from the 82 | /// `contents` pointer. 83 | #define array_extend(self, count, contents) \ 84 | _array__splice( \ 85 | (Array *)(self), array_elem_size(self), (self)->size, \ 86 | 0, count, contents \ 87 | ) 88 | 89 | /// Remove `old_count` elements from the array starting at the given `index`. At 90 | /// the same index, insert `new_count` new elements, reading their values from the 91 | /// `new_contents` pointer. 92 | #define array_splice(self, _index, old_count, new_count, new_contents) \ 93 | _array__splice( \ 94 | (Array *)(self), array_elem_size(self), _index, \ 95 | old_count, new_count, new_contents \ 96 | ) 97 | 98 | /// Insert one `element` into the array at the given `index`. 99 | #define array_insert(self, _index, element) \ 100 | _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element)) 101 | 102 | /// Remove one element from the array at the given `index`. 103 | #define array_erase(self, _index) \ 104 | _array__erase((Array *)(self), array_elem_size(self), _index) 105 | 106 | /// Pop the last element off the array, returning the element by value. 107 | #define array_pop(self) ((self)->contents[--(self)->size]) 108 | 109 | /// Assign the contents of one array to another, reallocating if necessary. 110 | #define array_assign(self, other) \ 111 | _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self)) 112 | 113 | /// Swap one array with another 114 | #define array_swap(self, other) \ 115 | _array__swap((Array *)(self), (Array *)(other)) 116 | 117 | /// Get the size of the array contents 118 | #define array_elem_size(self) (sizeof *(self)->contents) 119 | 120 | /// Search a sorted array for a given `needle` value, using the given `compare` 121 | /// callback to determine the order. 122 | /// 123 | /// If an existing element is found to be equal to `needle`, then the `index` 124 | /// out-parameter is set to the existing value's index, and the `exists` 125 | /// out-parameter is set to true. Otherwise, `index` is set to an index where 126 | /// `needle` should be inserted in order to preserve the sorting, and `exists` 127 | /// is set to false. 128 | #define array_search_sorted_with(self, compare, needle, _index, _exists) \ 129 | _array__search_sorted(self, 0, compare, , needle, _index, _exists) 130 | 131 | /// Search a sorted array for a given `needle` value, using integer comparisons 132 | /// of a given struct field (specified with a leading dot) to determine the order. 133 | /// 134 | /// See also `array_search_sorted_with`. 135 | #define array_search_sorted_by(self, field, needle, _index, _exists) \ 136 | _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists) 137 | 138 | /// Insert a given `value` into a sorted array, using the given `compare` 139 | /// callback to determine the order. 140 | #define array_insert_sorted_with(self, compare, value) \ 141 | do { \ 142 | unsigned _index, _exists; \ 143 | array_search_sorted_with(self, compare, &(value), &_index, &_exists); \ 144 | if (!_exists) array_insert(self, _index, value); \ 145 | } while (0) 146 | 147 | /// Insert a given `value` into a sorted array, using integer comparisons of 148 | /// a given struct field (specified with a leading dot) to determine the order. 149 | /// 150 | /// See also `array_search_sorted_by`. 151 | #define array_insert_sorted_by(self, field, value) \ 152 | do { \ 153 | unsigned _index, _exists; \ 154 | array_search_sorted_by(self, field, (value) field, &_index, &_exists); \ 155 | if (!_exists) array_insert(self, _index, value); \ 156 | } while (0) 157 | 158 | // Private 159 | 160 | typedef Array(void) Array; 161 | 162 | /// This is not what you're looking for, see `array_delete`. 163 | static inline void _array__delete(Array *self) { 164 | if (self->contents) { 165 | ts_free(self->contents); 166 | self->contents = NULL; 167 | self->size = 0; 168 | self->capacity = 0; 169 | } 170 | } 171 | 172 | /// This is not what you're looking for, see `array_erase`. 173 | static inline void _array__erase(Array *self, size_t element_size, 174 | uint32_t index) { 175 | assert(index < self->size); 176 | char *contents = (char *)self->contents; 177 | memmove(contents + index * element_size, contents + (index + 1) * element_size, 178 | (self->size - index - 1) * element_size); 179 | self->size--; 180 | } 181 | 182 | /// This is not what you're looking for, see `array_reserve`. 183 | static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) { 184 | if (new_capacity > self->capacity) { 185 | if (self->contents) { 186 | self->contents = ts_realloc(self->contents, new_capacity * element_size); 187 | } else { 188 | self->contents = ts_malloc(new_capacity * element_size); 189 | } 190 | self->capacity = new_capacity; 191 | } 192 | } 193 | 194 | /// This is not what you're looking for, see `array_assign`. 195 | static inline void _array__assign(Array *self, const Array *other, size_t element_size) { 196 | _array__reserve(self, element_size, other->size); 197 | self->size = other->size; 198 | memcpy(self->contents, other->contents, self->size * element_size); 199 | } 200 | 201 | /// This is not what you're looking for, see `array_swap`. 202 | static inline void _array__swap(Array *self, Array *other) { 203 | Array swap = *other; 204 | *other = *self; 205 | *self = swap; 206 | } 207 | 208 | /// This is not what you're looking for, see `array_push` or `array_grow_by`. 209 | static inline void _array__grow(Array *self, uint32_t count, size_t element_size) { 210 | uint32_t new_size = self->size + count; 211 | if (new_size > self->capacity) { 212 | uint32_t new_capacity = self->capacity * 2; 213 | if (new_capacity < 8) new_capacity = 8; 214 | if (new_capacity < new_size) new_capacity = new_size; 215 | _array__reserve(self, element_size, new_capacity); 216 | } 217 | } 218 | 219 | /// This is not what you're looking for, see `array_splice`. 220 | static inline void _array__splice(Array *self, size_t element_size, 221 | uint32_t index, uint32_t old_count, 222 | uint32_t new_count, const void *elements) { 223 | uint32_t new_size = self->size + new_count - old_count; 224 | uint32_t old_end = index + old_count; 225 | uint32_t new_end = index + new_count; 226 | assert(old_end <= self->size); 227 | 228 | _array__reserve(self, element_size, new_size); 229 | 230 | char *contents = (char *)self->contents; 231 | if (self->size > old_end) { 232 | memmove( 233 | contents + new_end * element_size, 234 | contents + old_end * element_size, 235 | (self->size - old_end) * element_size 236 | ); 237 | } 238 | if (new_count > 0) { 239 | if (elements) { 240 | memcpy( 241 | (contents + index * element_size), 242 | elements, 243 | new_count * element_size 244 | ); 245 | } else { 246 | memset( 247 | (contents + index * element_size), 248 | 0, 249 | new_count * element_size 250 | ); 251 | } 252 | } 253 | self->size += new_count - old_count; 254 | } 255 | 256 | /// A binary search routine, based on Rust's `std::slice::binary_search_by`. 257 | /// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`. 258 | #define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \ 259 | do { \ 260 | *(_index) = start; \ 261 | *(_exists) = false; \ 262 | uint32_t size = (self)->size - *(_index); \ 263 | if (size == 0) break; \ 264 | int comparison; \ 265 | while (size > 1) { \ 266 | uint32_t half_size = size / 2; \ 267 | uint32_t mid_index = *(_index) + half_size; \ 268 | comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \ 269 | if (comparison <= 0) *(_index) = mid_index; \ 270 | size -= half_size; \ 271 | } \ 272 | comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \ 273 | if (comparison == 0) *(_exists) = true; \ 274 | else if (comparison < 0) *(_index) += 1; \ 275 | } while (0) 276 | 277 | /// Helper macro for the `_sorted_by` routines below. This takes the left (existing) 278 | /// parameter by reference in order to work with the generic sorting function above. 279 | #define _compare_int(a, b) ((int)*(a) - (int)(b)) 280 | 281 | #ifdef _MSC_VER 282 | #pragma warning(pop) 283 | #elif defined(__GNUC__) || defined(__clang__) 284 | #pragma GCC diagnostic pop 285 | #endif 286 | 287 | #ifdef __cplusplus 288 | } 289 | #endif 290 | 291 | #endif // TREE_SITTER_ARRAY_H_ 292 | -------------------------------------------------------------------------------- /src/tree_sitter/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_PARSER_H_ 2 | #define TREE_SITTER_PARSER_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define ts_builtin_sym_error ((TSSymbol)-1) 13 | #define ts_builtin_sym_end 0 14 | #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 15 | 16 | #ifndef TREE_SITTER_API_H_ 17 | typedef uint16_t TSStateId; 18 | typedef uint16_t TSSymbol; 19 | typedef uint16_t TSFieldId; 20 | typedef struct TSLanguage TSLanguage; 21 | typedef struct TSLanguageMetadata TSLanguageMetadata; 22 | typedef struct TSLanguageMetadata { 23 | uint8_t major_version; 24 | uint8_t minor_version; 25 | uint8_t patch_version; 26 | } TSLanguageMetadata; 27 | #endif 28 | 29 | typedef struct { 30 | TSFieldId field_id; 31 | uint8_t child_index; 32 | bool inherited; 33 | } TSFieldMapEntry; 34 | 35 | // Used to index the field and supertype maps. 36 | typedef struct { 37 | uint16_t index; 38 | uint16_t length; 39 | } TSMapSlice; 40 | 41 | typedef struct { 42 | bool visible; 43 | bool named; 44 | bool supertype; 45 | } TSSymbolMetadata; 46 | 47 | typedef struct TSLexer TSLexer; 48 | 49 | struct TSLexer { 50 | int32_t lookahead; 51 | TSSymbol result_symbol; 52 | void (*advance)(TSLexer *, bool); 53 | void (*mark_end)(TSLexer *); 54 | uint32_t (*get_column)(TSLexer *); 55 | bool (*is_at_included_range_start)(const TSLexer *); 56 | bool (*eof)(const TSLexer *); 57 | void (*log)(const TSLexer *, const char *, ...); 58 | }; 59 | 60 | typedef enum { 61 | TSParseActionTypeShift, 62 | TSParseActionTypeReduce, 63 | TSParseActionTypeAccept, 64 | TSParseActionTypeRecover, 65 | } TSParseActionType; 66 | 67 | typedef union { 68 | struct { 69 | uint8_t type; 70 | TSStateId state; 71 | bool extra; 72 | bool repetition; 73 | } shift; 74 | struct { 75 | uint8_t type; 76 | uint8_t child_count; 77 | TSSymbol symbol; 78 | int16_t dynamic_precedence; 79 | uint16_t production_id; 80 | } reduce; 81 | uint8_t type; 82 | } TSParseAction; 83 | 84 | typedef struct { 85 | uint16_t lex_state; 86 | uint16_t external_lex_state; 87 | } TSLexMode; 88 | 89 | typedef struct { 90 | uint16_t lex_state; 91 | uint16_t external_lex_state; 92 | uint16_t reserved_word_set_id; 93 | } TSLexerMode; 94 | 95 | typedef union { 96 | TSParseAction action; 97 | struct { 98 | uint8_t count; 99 | bool reusable; 100 | } entry; 101 | } TSParseActionEntry; 102 | 103 | typedef struct { 104 | int32_t start; 105 | int32_t end; 106 | } TSCharacterRange; 107 | 108 | struct TSLanguage { 109 | uint32_t abi_version; 110 | uint32_t symbol_count; 111 | uint32_t alias_count; 112 | uint32_t token_count; 113 | uint32_t external_token_count; 114 | uint32_t state_count; 115 | uint32_t large_state_count; 116 | uint32_t production_id_count; 117 | uint32_t field_count; 118 | uint16_t max_alias_sequence_length; 119 | const uint16_t *parse_table; 120 | const uint16_t *small_parse_table; 121 | const uint32_t *small_parse_table_map; 122 | const TSParseActionEntry *parse_actions; 123 | const char * const *symbol_names; 124 | const char * const *field_names; 125 | const TSMapSlice *field_map_slices; 126 | const TSFieldMapEntry *field_map_entries; 127 | const TSSymbolMetadata *symbol_metadata; 128 | const TSSymbol *public_symbol_map; 129 | const uint16_t *alias_map; 130 | const TSSymbol *alias_sequences; 131 | const TSLexerMode *lex_modes; 132 | bool (*lex_fn)(TSLexer *, TSStateId); 133 | bool (*keyword_lex_fn)(TSLexer *, TSStateId); 134 | TSSymbol keyword_capture_token; 135 | struct { 136 | const bool *states; 137 | const TSSymbol *symbol_map; 138 | void *(*create)(void); 139 | void (*destroy)(void *); 140 | bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); 141 | unsigned (*serialize)(void *, char *); 142 | void (*deserialize)(void *, const char *, unsigned); 143 | } external_scanner; 144 | const TSStateId *primary_state_ids; 145 | const char *name; 146 | const TSSymbol *reserved_words; 147 | uint16_t max_reserved_word_set_size; 148 | uint32_t supertype_count; 149 | const TSSymbol *supertype_symbols; 150 | const TSMapSlice *supertype_map_slices; 151 | const TSSymbol *supertype_map_entries; 152 | TSLanguageMetadata metadata; 153 | }; 154 | 155 | static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) { 156 | uint32_t index = 0; 157 | uint32_t size = len - index; 158 | while (size > 1) { 159 | uint32_t half_size = size / 2; 160 | uint32_t mid_index = index + half_size; 161 | const TSCharacterRange *range = &ranges[mid_index]; 162 | if (lookahead >= range->start && lookahead <= range->end) { 163 | return true; 164 | } else if (lookahead > range->end) { 165 | index = mid_index; 166 | } 167 | size -= half_size; 168 | } 169 | const TSCharacterRange *range = &ranges[index]; 170 | return (lookahead >= range->start && lookahead <= range->end); 171 | } 172 | 173 | /* 174 | * Lexer Macros 175 | */ 176 | 177 | #ifdef _MSC_VER 178 | #define UNUSED __pragma(warning(suppress : 4101)) 179 | #else 180 | #define UNUSED __attribute__((unused)) 181 | #endif 182 | 183 | #define START_LEXER() \ 184 | bool result = false; \ 185 | bool skip = false; \ 186 | UNUSED \ 187 | bool eof = false; \ 188 | int32_t lookahead; \ 189 | goto start; \ 190 | next_state: \ 191 | lexer->advance(lexer, skip); \ 192 | start: \ 193 | skip = false; \ 194 | lookahead = lexer->lookahead; 195 | 196 | #define ADVANCE(state_value) \ 197 | { \ 198 | state = state_value; \ 199 | goto next_state; \ 200 | } 201 | 202 | #define ADVANCE_MAP(...) \ 203 | { \ 204 | static const uint16_t map[] = { __VA_ARGS__ }; \ 205 | for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \ 206 | if (map[i] == lookahead) { \ 207 | state = map[i + 1]; \ 208 | goto next_state; \ 209 | } \ 210 | } \ 211 | } 212 | 213 | #define SKIP(state_value) \ 214 | { \ 215 | skip = true; \ 216 | state = state_value; \ 217 | goto next_state; \ 218 | } 219 | 220 | #define ACCEPT_TOKEN(symbol_value) \ 221 | result = true; \ 222 | lexer->result_symbol = symbol_value; \ 223 | lexer->mark_end(lexer); 224 | 225 | #define END_STATE() return result; 226 | 227 | /* 228 | * Parse Table Macros 229 | */ 230 | 231 | #define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT) 232 | 233 | #define STATE(id) id 234 | 235 | #define ACTIONS(id) id 236 | 237 | #define SHIFT(state_value) \ 238 | {{ \ 239 | .shift = { \ 240 | .type = TSParseActionTypeShift, \ 241 | .state = (state_value) \ 242 | } \ 243 | }} 244 | 245 | #define SHIFT_REPEAT(state_value) \ 246 | {{ \ 247 | .shift = { \ 248 | .type = TSParseActionTypeShift, \ 249 | .state = (state_value), \ 250 | .repetition = true \ 251 | } \ 252 | }} 253 | 254 | #define SHIFT_EXTRA() \ 255 | {{ \ 256 | .shift = { \ 257 | .type = TSParseActionTypeShift, \ 258 | .extra = true \ 259 | } \ 260 | }} 261 | 262 | #define REDUCE(symbol_name, children, precedence, prod_id) \ 263 | {{ \ 264 | .reduce = { \ 265 | .type = TSParseActionTypeReduce, \ 266 | .symbol = symbol_name, \ 267 | .child_count = children, \ 268 | .dynamic_precedence = precedence, \ 269 | .production_id = prod_id \ 270 | }, \ 271 | }} 272 | 273 | #define RECOVER() \ 274 | {{ \ 275 | .type = TSParseActionTypeRecover \ 276 | }} 277 | 278 | #define ACCEPT_INPUT() \ 279 | {{ \ 280 | .type = TSParseActionTypeAccept \ 281 | }} 282 | 283 | #ifdef __cplusplus 284 | } 285 | #endif 286 | 287 | #endif // TREE_SITTER_PARSER_H_ 288 | -------------------------------------------------------------------------------- /test-awsconfig: -------------------------------------------------------------------------------- 1 | 2 | [default] 3 | region = us-west-2 4 | output = json 5 | [profile dev-user] 6 | region = us-east-1 7 | output = text 8 | 9 | [profile developers] 10 | role_arn = arn:aws:iam::123456789012:role/developers 11 | # foo 12 | source_profile = dev-user 13 | region = us-west-2 14 | output = json 15 | 16 | 17 | 18 | # foo 19 | 20 | -------------------------------------------------------------------------------- /test/corpus/main.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | one section 3 | ================================================================================ 4 | 5 | [a section title] 6 | foo = bar 7 | 8 | -------------------------------------------------------------------------------- 9 | 10 | (document 11 | (section 12 | (section_name 13 | (text)) 14 | (setting 15 | (setting_name) 16 | (setting_value)))) 17 | 18 | ================================================================================ 19 | setting value may be null/empty #9 20 | ================================================================================ 21 | 22 | [section 1] 23 | setting 1 = 24 | setting 2 = x 25 | setting 3 = 26 | # comment 27 | setting 4 = y 28 | setting-5 = 29 | 30 | [section 2] 31 | 32 | -------------------------------------------------------------------------------- 33 | 34 | (document 35 | (section 36 | (section_name 37 | (text)) 38 | (setting 39 | (setting_name)) 40 | (setting 41 | (setting_name) 42 | (setting_value)) 43 | (setting 44 | (setting_name)) 45 | (comment 46 | (text)) 47 | (setting 48 | (setting_name) 49 | (setting_value)) 50 | (setting 51 | (setting_name))) 52 | (section 53 | (section_name 54 | (text)))) 55 | 56 | ================================================================================ 57 | weird key names 58 | ================================================================================ 59 | 60 | # key with whitespace https://github.com/justinmk/tree-sitter-ini/issues/11 61 | [fruit.Date] 62 | taste = novel 63 | Trademark Issues="truly unlikely" 64 | ignore all white space = my value 65 | x = v 66 | 67 | -------------------------------------------------------------------------------- 68 | 69 | (document 70 | (comment 71 | (text)) 72 | (section 73 | (section_name 74 | (text)) 75 | (setting 76 | (setting_name) 77 | (setting_value)) 78 | (setting 79 | (setting_name) 80 | (setting_value)) 81 | (setting 82 | (setting_name) 83 | (setting_value)) 84 | (setting 85 | (setting_name) 86 | (setting_value)))) 87 | 88 | ================================================================================ 89 | many sections, some empty 90 | ================================================================================ 91 | 92 | # comment ... 93 | 94 | [a section title] 95 | foo = bar 96 | [section 2] 97 | foo = bar 98 | 99 | [section 3, which is empty] 100 | [section4] 101 | 2f_a3-x = not! an equals sign... 102 | foo = bar 103 | 104 | # Interesting note about duplicates in tree-sitter: 105 | 106 | # https://github.com/tree-sitter/tree-sitter/issues/130 107 | # foo 108 | 109 | 110 | --foo-- = bar 111 | ``foo`` = bar 112 | 113 | 114 | # a straggler comment... 115 | 116 | 117 | # another 118 | 119 | -------------------------------------------------------------------------------- 120 | 121 | (document 122 | (comment 123 | (text)) 124 | (section 125 | (section_name 126 | (text)) 127 | (setting 128 | (setting_name) 129 | (setting_value))) 130 | (section 131 | (section_name 132 | (text)) 133 | (setting 134 | (setting_name) 135 | (setting_value))) 136 | (section 137 | (section_name 138 | (text))) 139 | (section 140 | (section_name 141 | (text)) 142 | (setting 143 | (setting_name) 144 | (setting_value)) 145 | (setting 146 | (setting_name) 147 | (setting_value)) 148 | (comment 149 | (text)) 150 | (comment 151 | (text)) 152 | (comment 153 | (text)) 154 | (setting 155 | (setting_name) 156 | (setting_value)) 157 | (setting 158 | (setting_name) 159 | (setting_value))) 160 | (comment 161 | (text)) 162 | (comment 163 | (text))) 164 | 165 | ================================================================================ 166 | only comments 167 | ================================================================================ 168 | # 169 | ; 170 | # 171 | 172 | -------------------------------------------------------------------------------- 173 | 174 | (document 175 | (comment 176 | (text)) 177 | (comment 178 | (text)) 179 | (comment 180 | (text))) 181 | 182 | ================================================================================ 183 | mixed comments #5 184 | ================================================================================ 185 | 186 | ; 187 | # 1 188 | 189 | ; comment1 190 | 191 | [section1] 192 | ; comment2 193 | # comment3 194 | key1=val1 195 | 196 | # comment1 197 | [section2] 198 | # comment2 199 | ; comment3 200 | key1=val1 201 | 202 | -------------------------------------------------------------------------------- 203 | 204 | (document 205 | (comment 206 | (text)) 207 | (comment 208 | (text)) 209 | (comment 210 | (text)) 211 | (section 212 | (section_name 213 | (text)) 214 | (comment 215 | (text)) 216 | (comment 217 | (text)) 218 | (setting 219 | (setting_name) 220 | (setting_value))) 221 | (comment 222 | (text)) 223 | (section 224 | (section_name 225 | (text)) 226 | (comment 227 | (text)) 228 | (comment 229 | (text)) 230 | (setting 231 | (setting_name) 232 | (setting_value)))) 233 | 234 | ================================================================================ 235 | one section (empty) 236 | ================================================================================ 237 | [section with no content] 238 | 239 | -------------------------------------------------------------------------------- 240 | 241 | (document 242 | (section 243 | (section_name 244 | (text)))) 245 | 246 | ================================================================================ 247 | NOT a comment 248 | ================================================================================ 249 | [foo] 250 | bar = baz # not-a-comment 251 | zim = -1>3 ; not-a-comment 252 | 253 | 254 | -------------------------------------------------------------------------------- 255 | 256 | (document 257 | (section 258 | (section_name 259 | (text)) 260 | (setting 261 | (setting_name) 262 | (setting_value)) 263 | (setting 264 | (setting_name) 265 | (setting_value)))) 266 | 267 | ================================================================================ 268 | invalid section 269 | ================================================================================ 270 | [not a section] a = b # not-a-comment 271 | 272 | 273 | -------------------------------------------------------------------------------- 274 | 275 | (document 276 | (ERROR 277 | (text) 278 | (UNEXPECTED 'a') 279 | (UNEXPECTED 'b')) 280 | (comment (text))) 281 | 282 | ================================================================================ 283 | ~/.aws/config 284 | ================================================================================ 285 | # AWS config doc: 286 | # https://docs.aws.amazon.com/credref/latest/refdocs/file-format.html 287 | 288 | [default] 289 | region = us-west-2 290 | 291 | output = json 292 | 293 | [profile dev-user] 294 | # foo 295 | region = us-east-1 296 | output = text 297 | 298 | [profile developers] 299 | role_arn = arn:aws:iam::123456789012:role/developers 300 | # foo 301 | # foo 302 | source_profile = dev-user 303 | 304 | region = us-west-2 305 | output = json 306 | 307 | # foo 308 | 309 | -------------------------------------------------------------------------------- 310 | 311 | (document 312 | (comment 313 | (text)) 314 | (comment 315 | (text)) 316 | (section 317 | (section_name 318 | (text)) 319 | (setting 320 | (setting_name) 321 | (setting_value)) 322 | (setting 323 | (setting_name) 324 | (setting_value))) 325 | (section 326 | (section_name 327 | (text)) 328 | (comment 329 | (text)) 330 | (setting 331 | (setting_name) 332 | (setting_value)) 333 | (setting 334 | (setting_name) 335 | (setting_value))) 336 | (section 337 | (section_name 338 | (text)) 339 | (setting 340 | (setting_name) 341 | (setting_value)) 342 | (comment 343 | (text)) 344 | (comment 345 | (text)) 346 | (setting 347 | (setting_name) 348 | (setting_value)) 349 | (setting 350 | (setting_name) 351 | (setting_value)) 352 | (setting 353 | (setting_name) 354 | (setting_value))) 355 | (comment 356 | (text))) 357 | ================================================================================ 358 | global-parameters 359 | ================================================================================ 360 | # leading comment 361 | a=b 362 | # another comment 363 | [section] 364 | c=d 365 | 366 | -------------------------------------------------------------------------------- 367 | 368 | (document 369 | (comment 370 | (text)) 371 | (setting 372 | (setting_name) 373 | (setting_value)) 374 | (comment 375 | (text)) 376 | (section 377 | (section_name 378 | (text)) 379 | (setting 380 | (setting_name) 381 | (setting_value)))) 382 | --------------------------------------------------------------------------------