diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..72997b3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,60 @@ +version: 2 + +# The language manifests below (Cargo.toml, go.mod, pyproject.toml, +# Package.swift) are tree-sitter scaffolding marked linguist-generated. Take +# these bumps as a signal about the ecosystem, not as edits to keep: prefer +# raising the version in tree-sitter.json and regenerating. +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + commit-message: + prefix: ci + groups: + actions: + patterns: ['*'] + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + commit-message: + prefix: build + groups: + npm: + patterns: ['*'] + + - package-ecosystem: cargo + directory: / + schedule: + interval: weekly + commit-message: + prefix: build + groups: + cargo: + patterns: ['*'] + + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + commit-message: + prefix: build + + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + commit-message: + prefix: build + groups: + pip: + patterns: ['*'] + + - package-ecosystem: swift + directory: / + schedule: + interval: weekly + commit-message: + prefix: build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..19304c7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,186 @@ +name: release + +on: + push: + tags: ['v*'] + workflow_dispatch: + inputs: + tag: + description: >- + Existing tag to build and publish. Leave blank to build the default + branch and upload the artefacts without creating a release. + required: false + default: '' + +permissions: + contents: read + +concurrency: + group: release-${{ inputs.tag || github.ref }} + cancel-in-progress: false + +jobs: + # The version lives in five files that have to agree, and a tag that + # disagrees with them ships a mislabelled artefact. Fail before building. + version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.check.outputs.version }} + tag: ${{ steps.check.outputs.tag }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.tag }} + - id: check + run: | + version=$(jq -r .metadata.version tree-sitter.json) + status=0 + check() { + if [ "$2" != "$version" ]; then + echo "::error file=$1::version '$2' does not match tree-sitter.json $version" + status=1 + fi + } + first_version() { grep -m1 '^version = ' "$1" | sed 's/version = "\(.*\)"/\1/'; } + check package.json "$(jq -r .version package.json)" + check Cargo.toml "$(first_version Cargo.toml)" + check pyproject.toml "$(first_version pyproject.toml)" + check Makefile "$(sed -n 's/^VERSION := //p' Makefile)" + + tag='${{ inputs.tag }}' + if [ -z "$tag" ] && [ "$GITHUB_REF_TYPE" = tag ]; then + tag=$GITHUB_REF_NAME + fi + if [ -n "$tag" ] && [ "${tag#v}" != "$version" ]; then + echo "::error::tag $tag does not match version $version" + status=1 + fi + + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=$tag" >> "$GITHUB_OUTPUT" + exit $status + + build: + needs: version + name: ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - target: linux-x64 + os: ubuntu-latest + shell: bash + - target: linux-arm64 + os: ubuntu-24.04-arm + shell: bash + - target: macos-universal + os: macos-latest + shell: bash + - target: windows-x64 + os: windows-latest + shell: 'msys2 {0}' + defaults: + run: + shell: ${{ matrix.shell }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.tag }} + + # The Makefile's mingw32 branch wants a GNU toolchain, not MSVC. + - if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: mingw-w64-x86_64-gcc make zip + + # src/parser.c is committed, so this needs no tree-sitter CLI. + - name: build + run: | + if [ "$RUNNER_OS" = macOS ]; then + # ar refuses universal object files, so build each slice on its own + # and lipo the results together. + for arch in x86_64 arm64; do + make clean + make CFLAGS="-arch $arch" LDFLAGS="-arch $arch" + mkdir -p "slice-$arch" + mv libtree-sitter-applescript.a libtree-sitter-applescript.dylib "slice-$arch/" + done + lipo -create -output libtree-sitter-applescript.dylib slice-*/libtree-sitter-applescript.dylib + lipo -create -output libtree-sitter-applescript.a slice-*/libtree-sitter-applescript.a + lipo -info libtree-sitter-applescript.dylib libtree-sitter-applescript.a + else + make + fi + + # The artefact exists to be dlopen'd for this one symbol. + - name: exported symbol + run: | + if [ "$RUNNER_OS" = Windows ]; then + lib=libtree-sitter-applescript.dll.a + elif [ "$RUNNER_OS" = macOS ]; then + lib=libtree-sitter-applescript.dylib + else + lib=libtree-sitter-applescript.so + fi + nm -g --defined-only "$lib" | grep -q tree_sitter_applescript + + - name: stage + run: | + make install DESTDIR="$PWD/dist" PREFIX=/usr/local + # SOEXTVER is unset in the Makefile's mingw32 branch, so the shared + # library install lands an extra zero-version file. Drop it. + rm -f 'dist/usr/local/lib/libtree-sitter-applescript.' + doc=dist/usr/local/share/doc/tree-sitter-applescript + install -d "$doc" + install -m644 LICENSE README.md "$doc" + find dist \( -type f -o -type l \) | sort + + - name: archive + run: | + name=tree-sitter-applescript-${{ needs.version.outputs.version }}-${{ matrix.target }} + mkdir -p artifacts + if [ "$RUNNER_OS" = Windows ]; then + (cd dist/usr/local && zip -qr "../../../artifacts/$name.zip" .) + else + tar -czf "artifacts/$name.tar.gz" -C dist/usr/local . + fi + + - uses: actions/upload-artifact@v7 + with: + name: ${{ matrix.target }} + path: artifacts/* + if-no-files-found: error + + release: + if: needs.version.outputs.tag != '' + needs: [version, build] + runs-on: ubuntu-latest + permissions: + contents: write + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + steps: + - uses: actions/download-artifact@v8 + with: + path: artifacts + + - name: collect + run: | + mkdir -p dist + find artifacts -type f -exec mv {} dist/ \; + cd dist && sha256sum * > SHA256SUMS && cat SHA256SUMS + + - name: publish + run: | + tag=${{ needs.version.outputs.tag }} + # A tag like v2.1.0-rc1 is not a stable release. + case "$tag" in *-*) prerelease=--prerelease ;; *) prerelease= ;; esac + if gh release view "$tag" >/dev/null 2>&1; then + gh release upload "$tag" dist/* --clobber + else + gh release create "$tag" --title "$tag" --generate-notes $prerelease dist/* + fi diff --git a/Cargo.toml b/Cargo.toml index 1557b59..461baf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tree-sitter-applescript" description = "AppleScript grammar for tree-sitter" -version = "2.0.0" +version = "2.0.1" authors = ["Tom Waddington "] license = "MIT" readme = "README.md" diff --git a/Makefile b/Makefile index 29a826b..5828acf 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ LANGUAGE_NAME := tree-sitter-applescript HOMEPAGE_URL := https://github.com/waddie/tree-sitter-applescript -VERSION := 2.0.0 +VERSION := 2.0.1 DESCRIPTION := AppleScript grammar for tree-sitter # repository diff --git a/bindings/node/index.js b/bindings/node/index.js index 97a34e1..f276423 100644 --- a/bindings/node/index.js +++ b/bindings/node/index.js @@ -7,15 +7,20 @@ import { fileURLToPath } from "node:url"; const root = fileURLToPath(new URL("../..", import.meta.url)); -const binding = typeof process.versions.bun === "string" - // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time - ? await import(`${root}/prebuilds/${process.platform}-${process.arch}/tree-sitter-applescript.node`) - : (await import("node-gyp-build")).default(root); +const binding = + typeof process.versions.bun === "string" + ? // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time + await import( + `${root}/prebuilds/${process.platform}-${process.arch}/tree-sitter-applescript.node` + ) + : (await import("node-gyp-build")).default(root); try { - const nodeTypes = await import(`${root}/src/node-types.json`, { with: { type: "json" } }); + const nodeTypes = await import(`${root}/src/node-types.json`, { + with: { type: "json" }, + }); binding.nodeTypeInfo = nodeTypes.default; -} catch { } +} catch {} const queries = [ ["HIGHLIGHTS_QUERY", `${root}/queries/highlights.scm`], @@ -32,9 +37,9 @@ for (const [prop, path] of queries) { delete binding[prop]; try { binding[prop] = readFileSync(path, "utf8"); - } catch { } + } catch {} return binding[prop]; - } + }, }); } diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs index ae7c9b7..41d18ed 100644 --- a/bindings/rust/build.rs +++ b/bindings/rust/build.rs @@ -13,12 +13,16 @@ fn main() { if std::env::var("TARGET").unwrap() == "wasm32-unknown-unknown" { let Ok(wasm_headers) = std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS") else { - panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate"); + panic!( + "Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate" + ); }; let Ok(wasm_src) = std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_SRC").map(std::path::PathBuf::from) else { - panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_SRC must be set by the language crate"); + panic!( + "Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_SRC must be set by the language crate" + ); }; c_config.include(&wasm_headers); @@ -42,11 +46,15 @@ fn main() { c_config.compile("tree-sitter-applescript"); println!("cargo:rustc-check-cfg=cfg(with_highlights_query)"); - if !"queries/highlights.scm".is_empty() && std::path::Path::new("queries/highlights.scm").exists() { + if !"queries/highlights.scm".is_empty() + && std::path::Path::new("queries/highlights.scm").exists() + { println!("cargo:rustc-cfg=with_highlights_query"); } println!("cargo:rustc-check-cfg=cfg(with_injections_query)"); - if !"queries/injections.scm".is_empty() && std::path::Path::new("queries/injections.scm").exists() { + if !"queries/injections.scm".is_empty() + && std::path::Path::new("queries/injections.scm").exists() + { println!("cargo:rustc-cfg=with_injections_query"); } println!("cargo:rustc-check-cfg=cfg(with_locals_query)"); diff --git a/bindings/rust/lib.rs b/bindings/rust/lib.rs index 7c90fd1..fad8e68 100644 --- a/bindings/rust/lib.rs +++ b/bindings/rust/lib.rs @@ -2,7 +2,7 @@ // Copyright (c) 2025 Tom Waddington // See LICENSE file for full license text -//! This crate provides AppleScript language support for the [tree-sitter] parsing library. +//! This crate provides `AppleScript` language support for the [tree-sitter] parsing library. //! //! Typically, you will use the [`LANGUAGE`] constant to add this language to a //! tree-sitter [`Parser`], and then use the parser to parse some code: diff --git a/grammar.js b/grammar.js index b988ec5..0427967 100644 --- a/grammar.js +++ b/grammar.js @@ -3,20 +3,17 @@ // See LICENSE file for full license text export default grammar({ - name: 'applescript', + name: "applescript", - externals: $ => [ - $.method_call_label, // Objective-C method call: stringWithString: - $.path_to_command, // "path to" as atomic command - $.handler_label, // Objective-C handler label: doJava (when followed by :) + externals: ($) => [ + $.method_call_label, // Objective-C method call: stringWithString: + $.path_to_command, // "path to" as atomic command + $.handler_label, // Objective-C handler label: doJava (when followed by :) ], - extras: $ => [ - $.comment, - /[\s\f\uFEFF\u2060\u200B]|\\\r?\n|¬\r?\n/, - ], + extras: ($) => [$.comment, /[\s\f\uFEFF\u2060\u200B]|\\\r?\n|¬\r?\n/], - conflicts: $ => [ + conflicts: ($) => [ // Repeat statements vs expression statements [$.repeat_times, $.expression_statement], [$.transaction_statement, $.expression_statement], @@ -29,8 +26,21 @@ export default grammar({ [$.list, $.record], // Parameter and expression ambiguities - [$.direct_parameter, $.labeled_parameter, $.application_command, $.property_name, $._expression, $._identifier_or_keyword_token], - [$.direct_parameter, $.labeled_parameter, $.application_command, $.property_name, $._expression], + [ + $.direct_parameter, + $.labeled_parameter, + $.application_command, + $.property_name, + $._expression, + $._identifier_or_keyword_token, + ], + [ + $.direct_parameter, + $.labeled_parameter, + $.application_command, + $.property_name, + $._expression, + ], [$.parameter_list, $.property_name, $._expression], // Handler call ambiguities (only for contexts with parentheses) @@ -64,433 +74,481 @@ export default grammar({ ], rules: { - source_file: $ => repeat(choice( - $.property_statement, - $.handler_definition, - $._statement, - )), - - _statement: $ => choice( - $.tell_statement, - $.if_statement, - $.repeat_statement, - $.try_statement, - $.set_statement, - $.return_statement, - $.exit_statement, - $.error_statement, - $.considering_statement, - $.ignoring_statement, - $.timeout_statement, - $.transaction_statement, - $.use_statement, - $.script_statement, - $.application_command, - $.expression_statement, - ), + source_file: ($) => + repeat(choice($.property_statement, $.handler_definition, $._statement)), + + _statement: ($) => + choice( + $.tell_statement, + $.if_statement, + $.repeat_statement, + $.try_statement, + $.set_statement, + $.return_statement, + $.exit_statement, + $.error_statement, + $.considering_statement, + $.ignoring_statement, + $.timeout_statement, + $.transaction_statement, + $.use_statement, + $.script_statement, + $.application_command, + $.expression_statement, + ), // Comments - comment: $ => choice( - seq('--', /.*/), - seq('#', /.*/), - seq('(*', repeat(choice(/[^*]/, /\*[^)]/)), '*)'), - ), + comment: ($) => + choice( + seq("--", /.*/), + seq("#", /.*/), + seq("(*", repeat(choice(/[^*]/, /\*[^)]/)), "*)"), + ), // Tell Statement - tell_statement: $ => prec.right(choice( - seq( - 'tell', - field('target', $._expression), - 'to', - field('statement', $._statement), + tell_statement: ($) => + prec.right( + choice( + seq( + "tell", + field("target", $._expression), + "to", + field("statement", $._statement), + ), + seq( + "tell", + field("target", $._expression), + repeat1($._statement), + "end", + optional("tell"), + ), + seq("tell", field("target", $._expression)), + ), ), + + // If Statement + if_statement: ($) => seq( - 'tell', - field('target', $._expression), - repeat1($._statement), - 'end', - optional('tell'), + "if", + field("condition", $._expression), + "then", + choice( + $._statement, + seq( + repeat1($._statement), + repeat($.else_if_clause), + optional($.else_clause), + "end", + optional("if"), + ), + ), ), + + else_if_clause: ($) => seq( - 'tell', - field('target', $._expression), + "else", + "if", + field("condition", $._expression), + "then", + repeat1($._statement), ), - )), - // If Statement - if_statement: $ => seq( - 'if', - field('condition', $._expression), - 'then', + else_clause: ($) => seq("else", repeat1($._statement)), + + // Repeat Statement + repeat_statement: ($) => choice( - $._statement, + $.repeat_forever, + $.repeat_times, + $.repeat_until, + $.repeat_while, + $.repeat_with, + $.repeat_with_in, + ), + + repeat_forever: ($) => + prec.right( + seq("repeat", repeat1($._statement), "end", optional("repeat")), + ), + + repeat_times: ($) => + prec.right( + seq( + "repeat", + field("count", $._expression), + optional("times"), + repeat1($._statement), + "end", + optional("repeat"), + ), + ), + + repeat_until: ($) => + prec.right( seq( + "repeat", + "until", + field("condition", $._expression), repeat1($._statement), - repeat($.else_if_clause), - optional($.else_clause), - 'end', - optional('if'), + "end", + optional("repeat"), ), ), - ), - else_if_clause: $ => seq( - 'else', - 'if', - field('condition', $._expression), - 'then', - repeat1($._statement), - ), + repeat_while: ($) => + prec.right( + seq( + "repeat", + "while", + field("condition", $._expression), + repeat1($._statement), + "end", + optional("repeat"), + ), + ), - else_clause: $ => seq( - 'else', - repeat1($._statement), - ), + repeat_with: ($) => + prec.right( + seq( + "repeat", + "with", + field("variable", $.identifier), + "from", + field("start", $._expression), + "to", + field("end", $._expression), + optional(seq("by", field("step", $._expression))), + repeat1($._statement), + "end", + optional("repeat"), + ), + ), - // Repeat Statement - repeat_statement: $ => choice( - $.repeat_forever, - $.repeat_times, - $.repeat_until, - $.repeat_while, - $.repeat_with, - $.repeat_with_in, - ), - - repeat_forever: $ => prec.right(seq( - 'repeat', - repeat1($._statement), - 'end', - optional('repeat'), - )), - - repeat_times: $ => prec.right(seq( - 'repeat', - field('count', $._expression), - optional('times'), - repeat1($._statement), - 'end', - optional('repeat'), - )), - - repeat_until: $ => prec.right(seq( - 'repeat', - 'until', - field('condition', $._expression), - repeat1($._statement), - 'end', - optional('repeat'), - )), - - repeat_while: $ => prec.right(seq( - 'repeat', - 'while', - field('condition', $._expression), - repeat1($._statement), - 'end', - optional('repeat'), - )), - - repeat_with: $ => prec.right(seq( - 'repeat', - 'with', - field('variable', $.identifier), - 'from', - field('start', $._expression), - 'to', - field('end', $._expression), - optional(seq('by', field('step', $._expression))), - repeat1($._statement), - 'end', - optional('repeat'), - )), - - repeat_with_in: $ => prec.right(seq( - 'repeat', - 'with', - field('variable', $.identifier), - 'in', - field('list', $._expression), - repeat1($._statement), - 'end', - optional('repeat'), - )), + repeat_with_in: ($) => + prec.right( + seq( + "repeat", + "with", + field("variable", $.identifier), + "in", + field("list", $._expression), + repeat1($._statement), + "end", + optional("repeat"), + ), + ), // Try Statement - try_statement: $ => prec.right(seq( - 'try', - repeat1($._statement), - repeat($.on_error_clause), - 'end', - optional('try'), - )), - - on_error_clause: $ => seq( - 'on', - 'error', - optional(field('message_var', $.identifier)), - optional(seq('number', field('number_var', $.identifier))), - optional(seq('from', field('from_var', $.identifier))), - optional(seq('partial', 'result', field('partial_var', $.identifier))), - optional(seq('to', field('to_var', $.identifier))), - repeat1($._statement), - ), + try_statement: ($) => + prec.right( + seq( + "try", + repeat1($._statement), + repeat($.on_error_clause), + "end", + optional("try"), + ), + ), + + on_error_clause: ($) => + seq( + "on", + "error", + optional(field("message_var", $.identifier)), + optional(seq("number", field("number_var", $.identifier))), + optional(seq("from", field("from_var", $.identifier))), + optional(seq("partial", "result", field("partial_var", $.identifier))), + optional(seq("to", field("to_var", $.identifier))), + repeat1($._statement), + ), // Set Statement - set_statement: $ => seq( - choice('set', 'copy'), - field('target', $._expression), - 'to', - field('value', $._expression), - ), + set_statement: ($) => + seq( + choice("set", "copy"), + field("target", $._expression), + "to", + field("value", $._expression), + ), // Return Statement - return_statement: $ => prec.right(seq( - 'return', - optional(field('value', $._expression)), - )), + return_statement: ($) => + prec.right(seq("return", optional(field("value", $._expression)))), // Exit Statement - exit_statement: $ => prec.right(seq( - 'exit', - optional('repeat'), - )), + exit_statement: ($) => prec.right(seq("exit", optional("repeat"))), // Error Statement - error_statement: $ => prec.right(seq( - 'error', - optional(choice( - seq(field('message', $._expression), repeat($.error_clause)), - repeat1($.error_clause), - )), - )), - - error_clause: $ => choice( - seq('number', field('number', $._expression)), - seq('from', field('from', $._expression)), - seq('partial', 'result', field('partial', $._expression)), - seq('to', field('to', $._expression)), - ), - - // Considering/Ignoring Statements - considering_statement: $ => prec.right(seq( - 'considering', - $.considering_terms, - repeat1($._statement), - 'end', - optional('considering'), - )), - - ignoring_statement: $ => prec.right(seq( - 'ignoring', - $.considering_terms, - repeat1($._statement), - 'end', - optional('ignoring'), - )), - - considering_terms: $ => seq( - $.considering_term, - repeat(seq(optional(','), optional('and'), $.considering_term)), - ), - - considering_term: $ => choice( - 'case', - 'diacriticals', - 'expansion', - 'hyphens', - 'punctuation', - 'white space', - 'numeric strings', - ), - - // Timeout Statement - timeout_statement: $ => seq( - 'with', - 'timeout', - optional('of'), - field('duration', $._expression), - optional('seconds'), - repeat1($._statement), - 'end', - optional('timeout'), - ), - - // Transaction Statement - transaction_statement: $ => seq( - 'with', - 'transaction', - optional(seq(optional('of'), field('session', $._expression))), - repeat1($._statement), - 'end', - optional('transaction'), - ), + error_statement: ($) => + prec.right( + seq( + "error", + optional( + choice( + seq(field("message", $._expression), repeat($.error_clause)), + repeat1($.error_clause), + ), + ), + ), + ), - // Use Statement - use_statement: $ => seq( - 'use', + error_clause: ($) => choice( - seq('scripting', 'additions'), - seq('AppleScript', 'version', $.string), - seq('framework', $.string), + seq("number", field("number", $._expression)), + seq("from", field("from", $._expression)), + seq("partial", "result", field("partial", $._expression)), + seq("to", field("to", $._expression)), + ), + + // Considering/Ignoring Statements + considering_statement: ($) => + prec.right( seq( - optional(seq(field('alias', $.identifier), ':')), - 'script', - $.string, + "considering", + $.considering_terms, + repeat1($._statement), + "end", + optional("considering"), ), + ), + + ignoring_statement: ($) => + prec.right( seq( - optional('application'), - field('name', $.string), - optional(seq('version', field('version', $.string))), + "ignoring", + $.considering_terms, + repeat1($._statement), + "end", + optional("ignoring"), ), ), - ), - // Script Statement - script_statement: $ => prec.right(seq( - 'script', - field('name', $.identifier), - repeat(choice( - $.property_statement, - $.handler_definition, - $._statement, - )), - 'end', - optional('script'), - )), + considering_terms: ($) => + seq( + $.considering_term, + repeat(seq(optional(","), optional("and"), $.considering_term)), + ), - // Property Statement - property_statement: $ => seq( - 'property', - field('name', $.identifier), - ':', - field('value', $._expression), - ), + considering_term: ($) => + choice( + "case", + "diacriticals", + "expansion", + "hyphens", + "punctuation", + "white space", + "numeric strings", + ), - // Handler Definition - handler_definition: $ => choice( - $.simple_handler, - $.positional_handler, - $.labeled_handler, - ), - - simple_handler: $ => prec.right(10, seq( - 'on', - field('name', $.identifier), - '(', - optional($.parameter_list), - ')', - repeat1($._statement), - 'end', - optional(field('end_name', $.identifier)), - )), - - positional_handler: $ => prec.right(10, choice( - // Regular positional handler + // Timeout Statement + timeout_statement: ($) => seq( - 'on', - field('name', $.identifier), - optional($.direct_parameter), - repeat($.labeled_parameter), + "with", + "timeout", + optional("of"), + field("duration", $._expression), + optional("seconds"), repeat1($._statement), - 'end', - optional(field('end_name', $.identifier)), + "end", + optional("timeout"), ), - // Objective-C style positional handler: on label:param label:param + + // Transaction Statement + transaction_statement: ($) => seq( - 'on', - field('name', alias($.handler_label, $.identifier)), - alias($.objc_first_parameter, $.labeled_parameter), - repeat(alias($.objc_parameter, $.labeled_parameter)), + "with", + "transaction", + optional(seq(optional("of"), field("session", $._expression))), repeat1($._statement), - 'end', - optional(field('end_name', $.identifier)), - ), - )), - - labeled_handler: $ => prec.right(10, seq( - 'to', - field('name', $.identifier), - optional($.direct_parameter), - repeat($.labeled_parameter), - repeat1($._statement), - 'end', - optional(field('end_name', $.identifier)), - )), - - direct_parameter: $ => prec(1, field('parameter', choice( - $.identifier, - $.string, - $.number, - $.every_document, - ))), - - labeled_parameter: $ => prec(2, choice( - seq( - field('label', $.identifier), - field('parameter', choice($.identifier, $._expression)), + "end", + optional("transaction"), ), + + // Use Statement + use_statement: ($) => seq( - field('label', $.identifier), - ':', - field('parameter', choice($.identifier, $._expression)), + "use", + choice( + seq("scripting", "additions"), + seq("AppleScript", "version", $.string), + seq("framework", $.string), + seq( + optional(seq(field("alias", $.identifier), ":")), + "script", + $.string, + ), + seq( + optional("application"), + field("name", $.string), + optional(seq("version", field("version", $.string))), + ), + ), + ), + + // Script Statement + script_statement: ($) => + prec.right( + seq( + "script", + field("name", $.identifier), + repeat( + choice($.property_statement, $.handler_definition, $._statement), + ), + "end", + optional("script"), + ), ), - // Objective-C style: handler_label appears as identifier in parse tree + + // Property Statement + property_statement: ($) => seq( - field('label', alias($.handler_label, $.identifier)), - ':', - field('parameter', choice($.identifier, $._expression)), + "property", + field("name", $.identifier), + ":", + field("value", $._expression), + ), + + // Handler Definition + handler_definition: ($) => + choice($.simple_handler, $.positional_handler, $.labeled_handler), + + simple_handler: ($) => + prec.right( + 10, + seq( + "on", + field("name", $.identifier), + "(", + optional($.parameter_list), + ")", + repeat1($._statement), + "end", + optional(field("end_name", $.identifier)), + ), + ), + + positional_handler: ($) => + prec.right( + 10, + choice( + // Regular positional handler + seq( + "on", + field("name", $.identifier), + optional($.direct_parameter), + repeat($.labeled_parameter), + repeat1($._statement), + "end", + optional(field("end_name", $.identifier)), + ), + // Objective-C style positional handler: on label:param label:param + seq( + "on", + field("name", alias($.handler_label, $.identifier)), + alias($.objc_first_parameter, $.labeled_parameter), + repeat(alias($.objc_parameter, $.labeled_parameter)), + repeat1($._statement), + "end", + optional(field("end_name", $.identifier)), + ), + ), + ), + + labeled_handler: ($) => + prec.right( + 10, + seq( + "to", + field("name", $.identifier), + optional($.direct_parameter), + repeat($.labeled_parameter), + repeat1($._statement), + "end", + optional(field("end_name", $.identifier)), + ), + ), + + direct_parameter: ($) => + prec( + 1, + field( + "parameter", + choice($.identifier, $.string, $.number, $.every_document), + ), + ), + + labeled_parameter: ($) => + prec( + 2, + choice( + seq( + field("label", $.identifier), + field("parameter", choice($.identifier, $._expression)), + ), + seq( + field("label", $.identifier), + ":", + field("parameter", choice($.identifier, $._expression)), + ), + // Objective-C style: handler_label appears as identifier in parse tree + seq( + field("label", alias($.handler_label, $.identifier)), + ":", + field("parameter", choice($.identifier, $._expression)), + ), + ), ), - )), // Objective-C style first parameter (label is implicit from handler name) - objc_first_parameter: $ => seq( - ':', - field('parameter', $.identifier), - ), + objc_first_parameter: ($) => seq(":", field("parameter", $.identifier)), // Objective-C style subsequent parameters - objc_parameter: $ => seq( - field('label', alias($.handler_label, $.identifier)), - ':', - field('parameter', $.identifier), - ), + objc_parameter: ($) => + seq( + field("label", alias($.handler_label, $.identifier)), + ":", + field("parameter", $.identifier), + ), - parameter_list: $ => seq( - $.identifier, - repeat(seq(',', $.identifier)), - ), + parameter_list: ($) => seq($.identifier, repeat(seq(",", $.identifier))), // Known multi-word commands (tokens for commands without keywords, sequences for those with keywords) - system_attribute: $ => token(prec(10, seq('system', /\s+/, 'attribute'))), - do_shell_script: $ => token(prec(10, seq('do', /\s+/, 'shell', /\s+/, 'script'))), - do_javascript: $ => token(prec(10, seq('do', /\s+/, /[Jj]ava[Ss]cript/))), + system_attribute: ($) => token(prec(10, seq("system", /\s+/, "attribute"))), + do_shell_script: ($) => + token(prec(10, seq("do", /\s+/, "shell", /\s+/, "script"))), + do_javascript: ($) => token(prec(10, seq("do", /\s+/, /[Jj]ava[Ss]cript/))), // path_to: Disabled for now due to conflicts with "POSIX path of" expressions - write_text: $ => token(prec(10, seq('write', /\s+/, 'text'))), - create_window: $ => token(prec(10, seq('create', /\s+/, 'window'))), - create_tab: $ => token(prec(10, seq('create', /\s+/, 'tab'))), + write_text: ($) => token(prec(10, seq("write", /\s+/, "text"))), + create_window: ($) => token(prec(10, seq("create", /\s+/, "window"))), + create_tab: ($) => token(prec(10, seq("create", /\s+/, "tab"))), // Known multi-word property names (common Terminal.app and other app properties) - current_session: $ => token(prec(10, seq('current', /\s+/, 'session'))), - current_window: $ => token(prec(10, seq('current', /\s+/, 'window'))), - current_tab: $ => token(prec(10, seq('current', /\s+/, 'tab'))), - first_window: $ => token(prec(10, seq('first', /\s+/, 'window'))), - first_process: $ => token(prec(10, seq('first', /\s+/, 'process'))), - first_button: $ => token(prec(10, seq('first', /\s+/, 'button'))), - first_document: $ => token(prec(10, seq('first', /\s+/, 'document'))), - first_item: $ => token(prec(10, seq('first', /\s+/, 'item'))), - front_document: $ => token(prec(10, seq('front', /\s+/, 'document'))), - front_window: $ => token(prec(10, seq('front', /\s+/, 'window'))), - selected_tab: $ => token(prec(10, seq('selected', /\s+/, 'tab'))), - selected_window: $ => token(prec(10, seq('selected', /\s+/, 'window'))), - used_script_library_files: $ => token(prec(100, seq('used', /\s+/, 'script', /\s+/, 'library', /\s+/, 'files'))), - every_item: $ => token(prec(10, seq('every', /\s+/, 'item'))), - every_window: $ => token(prec(10, seq('every', /\s+/, 'window'))), - every_document: $ => token(prec(10, seq('every', /\s+/, 'document'))), + current_session: ($) => token(prec(10, seq("current", /\s+/, "session"))), + current_window: ($) => token(prec(10, seq("current", /\s+/, "window"))), + current_tab: ($) => token(prec(10, seq("current", /\s+/, "tab"))), + first_window: ($) => token(prec(10, seq("first", /\s+/, "window"))), + first_process: ($) => token(prec(10, seq("first", /\s+/, "process"))), + first_button: ($) => token(prec(10, seq("first", /\s+/, "button"))), + first_document: ($) => token(prec(10, seq("first", /\s+/, "document"))), + first_item: ($) => token(prec(10, seq("first", /\s+/, "item"))), + front_document: ($) => token(prec(10, seq("front", /\s+/, "document"))), + front_window: ($) => token(prec(10, seq("front", /\s+/, "window"))), + selected_tab: ($) => token(prec(10, seq("selected", /\s+/, "tab"))), + selected_window: ($) => token(prec(10, seq("selected", /\s+/, "window"))), + used_script_library_files: ($) => + token( + prec( + 100, + seq("used", /\s+/, "script", /\s+/, "library", /\s+/, "files"), + ), + ), + every_item: ($) => token(prec(10, seq("every", /\s+/, "item"))), + every_window: ($) => token(prec(10, seq("every", /\s+/, "window"))), + every_document: ($) => token(prec(10, seq("every", /\s+/, "document"))), // UI Scripting patterns - scroll_area: $ => token(prec(10, seq('scroll', /\s+/, 'area'))), - radio_group: $ => token(prec(10, seq('radio', /\s+/, 'group'))), - radio_button: $ => token(prec(10, seq('radio', /\s+/, 'button'))), + scroll_area: ($) => token(prec(10, seq("scroll", /\s+/, "area"))), + radio_group: ($) => token(prec(10, seq("radio", /\s+/, "group"))), + radio_button: ($) => token(prec(10, seq("radio", /\s+/, "button"))), // Application Command // @@ -499,324 +557,423 @@ export default grammar({ // command name, to an argument, or to neither. Flattening them into a // single prec.right forces a new conflict declaration at every site that // consumes a command, so leave them alone. - application_command: $ => choice( - // Known multi-word commands (highest precedence). path_to_command is - // deliberately absent: it is only used by path_to_expression. - prec.right(5, seq( - field('command', choice( - $.system_attribute, - $.do_shell_script, - $.do_javascript, - $.write_text, - $.create_window, - $.create_tab, - )), - repeat(choice($.direct_parameter, $.labeled_parameter, $._expression)), - optional(choice($.with_clause, $.without_clause, $.using_clause)), - )), - // A bare identifier needs at least one argument, or it is just an - // identifier expression. - prec.right(2, seq( - field('command', choice( - $.identifier, - alias(seq($.identifier, repeat1($.identifier)), $.property_name), - )), - repeat1(choice($.direct_parameter, $.labeled_parameter, $._expression)), - optional(choice($.with_clause, $.without_clause, $.using_clause)), - )), - // Multi-word command without arguments (lower precedence). - prec.right(1, seq( - field('command', alias(seq($.identifier, repeat1($.identifier)), $.property_name)), - optional(choice($.with_clause, $.without_clause, $.using_clause)), - )), - ), - - with_clause: $ => seq( - 'with', - field('option', $.property_name), - optional(field('value', $._expression)), - ), - - without_clause: $ => seq( - 'without', - field('option', $.property_name), - ), - - using_clause: $ => seq( - 'using', - field('modifiers', choice( - prec.dynamic(1, $.property_name), // Prefer multi-word like "command down" - $._expression, - )), - ), + application_command: ($) => + choice( + // Known multi-word commands (highest precedence). path_to_command is + // deliberately absent: it is only used by path_to_expression. + prec.right( + 5, + seq( + field( + "command", + choice( + $.system_attribute, + $.do_shell_script, + $.do_javascript, + $.write_text, + $.create_window, + $.create_tab, + ), + ), + repeat( + choice($.direct_parameter, $.labeled_parameter, $._expression), + ), + optional(choice($.with_clause, $.without_clause, $.using_clause)), + ), + ), + // A bare identifier needs at least one argument, or it is just an + // identifier expression. + prec.right( + 2, + seq( + field( + "command", + choice( + $.identifier, + alias( + seq($.identifier, repeat1($.identifier)), + $.property_name, + ), + ), + ), + repeat1( + choice($.direct_parameter, $.labeled_parameter, $._expression), + ), + optional(choice($.with_clause, $.without_clause, $.using_clause)), + ), + ), + // Multi-word command without arguments (lower precedence). + prec.right( + 1, + seq( + field( + "command", + alias(seq($.identifier, repeat1($.identifier)), $.property_name), + ), + optional(choice($.with_clause, $.without_clause, $.using_clause)), + ), + ), + ), + + with_clause: ($) => + seq( + "with", + field("option", $.property_name), + optional(field("value", $._expression)), + ), + + without_clause: ($) => seq("without", field("option", $.property_name)), + + using_clause: ($) => + seq( + "using", + field( + "modifiers", + choice( + prec.dynamic(1, $.property_name), // Prefer multi-word like "command down" + $._expression, + ), + ), + ), // Property name (can be multi-word) - property_name: $ => choice( - // Known multi-word property tokens (highest precedence) - $.current_session, - $.current_window, - $.current_tab, - $.first_window, - $.first_process, - $.first_button, - $.first_document, - $.first_item, - $.front_document, - $.front_window, - $.selected_tab, - $.selected_window, - $.every_item, - $.every_window, - $.every_document, - $.used_script_library_files, - $.scroll_area, - $.radio_group, - $.radio_button, - // Generic multi-word properties (any sequence of 2+ identifiers/keywords) - // Use dynamic precedence to prefer this over keyword matches in expression contexts - prec.dynamic(2, seq($._identifier_or_keyword_token, repeat1($._identifier_or_keyword_token))), - // Single identifier (lowest precedence) - prec.dynamic(-1, $.identifier), - ), + property_name: ($) => + choice( + // Known multi-word property tokens (highest precedence) + $.current_session, + $.current_window, + $.current_tab, + $.first_window, + $.first_process, + $.first_button, + $.first_document, + $.first_item, + $.front_document, + $.front_window, + $.selected_tab, + $.selected_window, + $.every_item, + $.every_window, + $.every_document, + $.used_script_library_files, + $.scroll_area, + $.radio_group, + $.radio_button, + // Generic multi-word properties (any sequence of 2+ identifiers/keywords) + // Use dynamic precedence to prefer this over keyword matches in expression contexts + prec.dynamic( + 2, + seq( + $._identifier_or_keyword_token, + repeat1($._identifier_or_keyword_token), + ), + ), + // Single identifier (lowest precedence) + prec.dynamic(-1, $.identifier), + ), // Identifier or keyword used as identifier (for property names that contain keywords) - _identifier_or_keyword_token: $ => choice( - $.identifier, - alias('script', $.identifier), - alias('text', $.identifier), - alias('library', $.identifier), - alias('used', $.identifier), - alias('source', $.identifier), - alias('files', $.identifier), - alias('line', $.identifier), - alias('endings', $.identifier), - ), + _identifier_or_keyword_token: ($) => + choice( + $.identifier, + alias("script", $.identifier), + alias("text", $.identifier), + alias("library", $.identifier), + alias("used", $.identifier), + alias("source", $.identifier), + alias("files", $.identifier), + alias("line", $.identifier), + alias("endings", $.identifier), + ), // Expression Statement - expression_statement: $ => $._expression, + expression_statement: ($) => $._expression, // Objective-C Method Call (AppleScriptObjC) - method_call: $ => prec.right(16, seq( - field('object', $._expression), - '\'s', - field('method', $.method_call_label), - ':', - field('argument', $._expression), - // TODO: Support multiple labeled parameters - )), + method_call: ($) => + prec.right( + 16, + seq( + field("object", $._expression), + "'s", + field("method", $.method_call_label), + ":", + field("argument", $._expression), + // TODO: Support multiple labeled parameters + ), + ), // Path to expression (special case of command used as expression) - path_to_expression: $ => prec(14, seq( - $.path_to_command, - choice( - prec.dynamic(1, $.property_name), // Prefer multi-word properties - $._expression, // Fallback to any expression + path_to_expression: ($) => + prec( + 14, + seq( + $.path_to_command, + choice( + prec.dynamic(1, $.property_name), // Prefer multi-word properties + $._expression, // Fallback to any expression + ), + ), ), - )), // Expressions - _expression: $ => choice( - $.binary_expression, - $.unary_expression, - $.type_cast, // Type cast with 'as' keyword - $.parenthesized_expression, - $.application_expression, - $.path_to_expression, // "path to" command in expression context - $.method_call, // Must be before property_access (higher precedence) - $.handler_call, // Must be before property_access (uses same 's syntax) - $.property_access, - $.element_access, - $.list, - $.record, - $.property_name, // Multi-word properties - no negative precedence - $.identifier, - $.string, - $.number, - $.boolean, - $.missing_value, - $.current_application, - $.script_reference, - $.reference_expression, - ), - - binary_expression: $ => { + _expression: ($) => + choice( + $.binary_expression, + $.unary_expression, + $.type_cast, // Type cast with 'as' keyword + $.parenthesized_expression, + $.application_expression, + $.path_to_expression, // "path to" command in expression context + $.method_call, // Must be before property_access (higher precedence) + $.handler_call, // Must be before property_access (uses same 's syntax) + $.property_access, + $.element_access, + $.list, + $.record, + $.property_name, // Multi-word properties - no negative precedence + $.identifier, + $.string, + $.number, + $.boolean, + $.missing_value, + $.current_application, + $.script_reference, + $.reference_expression, + ), + + binary_expression: ($) => { const table = [ - [prec.left, 12, '^'], - [prec.left, 11, choice('*', '/', 'div', 'mod', '÷')], - [prec.left, 10, choice('+', '-', '&')], - [prec.left, 7, choice('and', 'or')], - [prec.left, 6, choice( - '=', '≠', '>', '<', '≥', '≤', '>=', '<=', - 'equals', 'is equal to', 'is equal', 'equal to', - 'is not', 'is not equal to', 'is not equal', 'isn\'t', 'isn\'t equal to', - 'is greater than', 'greater than', 'comes after', - 'is less than', 'less than', 'comes before', - 'is greater than or equal to', 'is greater than or equal', - 'is less than or equal to', 'is less than or equal', - 'is', - )], - [prec.left, 5, choice( - 'contains', 'contain', 'does not contain', 'doesn\'t contain', - 'is in', 'is contained by', - 'is not in', 'is not contained by', - 'starts with', 'start with', 'begins with', 'begin with', - 'ends with', 'end with', - )], + [prec.left, 12, "^"], + [prec.left, 11, choice("*", "/", "div", "mod", "÷")], + [prec.left, 10, choice("+", "-", "&")], + [prec.left, 7, choice("and", "or")], + [ + prec.left, + 6, + choice( + "=", + "≠", + ">", + "<", + "≥", + "≤", + ">=", + "<=", + "equals", + "is equal to", + "is equal", + "equal to", + "is not", + "is not equal to", + "is not equal", + "isn't", + "isn't equal to", + "is greater than", + "greater than", + "comes after", + "is less than", + "less than", + "comes before", + "is greater than or equal to", + "is greater than or equal", + "is less than or equal to", + "is less than or equal", + "is", + ), + ], + [ + prec.left, + 5, + choice( + "contains", + "contain", + "does not contain", + "doesn't contain", + "is in", + "is contained by", + "is not in", + "is not contained by", + "starts with", + "start with", + "begins with", + "begin with", + "ends with", + "end with", + ), + ], ]; - return choice(...table.map(([fn, precedence, operator]) => - fn(precedence, seq( - field('left', $._expression), - field('operator', operator), - field('right', $._expression), - )) - )); + return choice( + ...table.map(([fn, precedence, operator]) => + fn( + precedence, + seq( + field("left", $._expression), + field("operator", operator), + field("right", $._expression), + ), + ), + ), + ); }, // Type cast expression (e.g., "x as text", "(path to home) as alias") - type_cast: $ => prec.left(4, seq( - field('value', $._expression), - 'as', - field('type', $.identifier), - )), - - unary_expression: $ => choice( - prec(13, seq('-', field('operand', $._expression))), - prec(13, seq('+', field('operand', $._expression))), - prec(8, seq('not', field('operand', $._expression))), - ), - - parenthesized_expression: $ => seq( - '(', + type_cast: ($) => + prec.left( + 4, + seq(field("value", $._expression), "as", field("type", $.identifier)), + ), + + unary_expression: ($) => choice( - $._expression, - $.application_command, // Allow commands in parentheses (e.g., for command results) + prec(13, seq("-", field("operand", $._expression))), + prec(13, seq("+", field("operand", $._expression))), + prec(8, seq("not", field("operand", $._expression))), + ), + + parenthesized_expression: ($) => + seq( + "(", + choice( + $._expression, + $.application_command, // Allow commands in parentheses (e.g., for command results) + ), + ")", ), - ')', - ), - application_expression: $ => prec(14, seq( - 'application', - field('name', choice($.string, $.identifier)), - )), + application_expression: ($) => + prec( + 14, + seq("application", field("name", choice($.string, $.identifier))), + ), - property_access: $ => prec.left(15, seq( - field('object', $._expression), - '\'s', - field('property', $.property_name), - )), + property_access: ($) => + prec.left( + 15, + seq( + field("object", $._expression), + "'s", + field("property", $.property_name), + ), + ), - element_access: $ => prec.left(15, choice( - // Possessive form: object's element [index] - seq( - field('object', $._expression), - '\'s', - field('element', $.property_name), - optional(field('index', $.number)), + element_access: ($) => + prec.left( + 15, + choice( + // Possessive form: object's element [index] + seq( + field("object", $._expression), + "'s", + field("element", $.property_name), + optional(field("index", $.number)), + ), + // Of form: element [index] of object + seq( + field("element", $.property_name), + choice( + seq( + field("index", $.number), + "of", + field("object", $._expression), + ), + seq("of", field("object", $._expression)), + field("index", $.number), // Allow implicit object (e.g., "window 1") + ), + ), + ), ), - // Of form: element [index] of object - seq( - field('element', $.property_name), + + handler_call: ($) => + prec.dynamic( + 5, choice( - seq(field('index', $.number), 'of', field('object', $._expression)), - seq('of', field('object', $._expression)), - field('index', $.number), // Allow implicit object (e.g., "window 1") - ), - ), - )), - - handler_call: $ => prec.dynamic(5, choice( - // With object: obj's handler(args) - prec.left(16, seq( - field('object', $._expression), - '\'s', - field('name', $.identifier), - '(', - optional($.argument_list), - ')', - )), - // Without object: handler(args) - prec(3, seq( - field('name', $.identifier), - '(', - optional($.argument_list), - ')', - )), - )), - - argument_list: $ => seq( - $._expression, - repeat(seq(',', $._expression)), - ), + // With object: obj's handler(args) + prec.left( + 16, + seq( + field("object", $._expression), + "'s", + field("name", $.identifier), + "(", + optional($.argument_list), + ")", + ), + ), + // Without object: handler(args) + prec( + 3, + seq( + field("name", $.identifier), + "(", + optional($.argument_list), + ")", + ), + ), + ), + ), + + argument_list: ($) => seq($._expression, repeat(seq(",", $._expression))), - reference_expression: $ => seq( - choice('a ref to', 'a reference to', 'ref to', 'reference to'), - field('target', $._expression), - ), + reference_expression: ($) => + seq( + choice("a ref to", "a reference to", "ref to", "reference to"), + field("target", $._expression), + ), // Literals - identifier: $ => choice( - /[A-Za-z_][A-Za-z0-9_]*/, - seq('|', /[^|]+/, '|'), - ), - - string: $ => seq( - '"', - repeat(choice( - /[^"\\]+/, - /\\./, - )), - '"', - ), - - number: $ => choice( - $.integer, - $.real, - ), - - integer: $ => /[0-9]+/, - - real: $ => choice( - /[0-9]+\.[0-9]+/, - /[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+/, - ), - - boolean: $ => choice('true', 'false', 'yes', 'no'), - - missing_value: $ => seq('missing', 'value'), - - current_application: $ => token(seq('current', /\s+/, 'application')), - - script_reference: $ => seq('script', $.string), - - list: $ => seq( - '{', - optional(seq( - $._expression, - repeat(seq(',', $._expression)), - optional(','), - )), - '}', - ), - - record: $ => seq( - '{', - optional(seq( - $.record_entry, - repeat(seq(',', $.record_entry)), - optional(','), - )), - '}', - ), - - record_entry: $ => prec.dynamic(3, seq( - field('key', choice( - prec.dynamic(1, $.identifier), // Prefer plain identifier for single-word keys - $.property_name, // Use property_name for multi-word keys - $.string - )), - ':', - field('value', $._expression), - )), + identifier: ($) => choice(/[A-Za-z_][A-Za-z0-9_]*/, seq("|", /[^|]+/, "|")), + + string: ($) => seq('"', repeat(choice(/[^"\\]+/, /\\./)), '"'), + + number: ($) => choice($.integer, $.real), + + integer: ($) => /[0-9]+/, + + real: ($) => choice(/[0-9]+\.[0-9]+/, /[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+/), + + boolean: ($) => choice("true", "false", "yes", "no"), + + missing_value: ($) => seq("missing", "value"), + + current_application: ($) => token(seq("current", /\s+/, "application")), + + script_reference: ($) => seq("script", $.string), + + list: ($) => + seq( + "{", + optional( + seq($._expression, repeat(seq(",", $._expression)), optional(",")), + ), + "}", + ), + + record: ($) => + seq( + "{", + optional( + seq($.record_entry, repeat(seq(",", $.record_entry)), optional(",")), + ), + "}", + ), + + record_entry: ($) => + prec.dynamic( + 3, + seq( + field( + "key", + choice( + prec.dynamic(1, $.identifier), // Prefer plain identifier for single-word keys + $.property_name, // Use property_name for multi-word keys + $.string, + ), + ), + ":", + field("value", $._expression), + ), + ), }, }); diff --git a/package.json b/package.json index 6f51dc3..f3bb928 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tree-sitter-applescript", - "version": "2.0.0", + "version": "2.0.1", "description": "AppleScript grammar for tree-sitter", "type": "module", "repository": { diff --git a/pyproject.toml b/pyproject.toml index a4fafa9..e3ffc6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "tree-sitter-applescript" description = "AppleScript grammar for tree-sitter" -version = "2.0.0" +version = "2.0.1" keywords = ["incremental", "parsing", "tree-sitter", "applescript"] classifiers = [ "Intended Audience :: Developers", diff --git a/queries/highlights.scm b/queries/highlights.scm index cf2e16d..d523f6a 100644 --- a/queries/highlights.scm +++ b/queries/highlights.scm @@ -38,8 +38,8 @@ "and" "or" "not" - "as" -] @keyword + "as"] +@keyword ; Control flow keywords [ @@ -50,91 +50,131 @@ "exit" "return" "try" - "error" -] @keyword.control + "error"] +@keyword.control ; Boolean literals -(boolean) @constant.builtin.boolean +(boolean) +@constant.builtin.boolean ; Special constants -(missing_value) @constant.builtin +(missing_value) +@constant.builtin ; Current application (special constant) -(current_application) @constant.builtin +(current_application) +@constant.builtin ; Numbers -(integer) @constant.numeric.integer -(real) @constant.numeric.float +(integer) +@constant.numeric.integer +(real) +@constant.numeric.float ; Strings -(string) @string +(string) +@string ; Comments -(comment) @comment +(comment) +@comment ; Identifiers -(identifier) @variable +(identifier) +@variable ; Function/Handler definitions (simple_handler - name: (identifier) @function) + name: + (identifier) + @function) (positional_handler - name: (identifier) @function) + name: + (identifier) + @function) (labeled_handler - name: (identifier) @function) + name: + (identifier) + @function) ; Script object definitions (like classes) (script_statement - name: (identifier) @type) + name: + (identifier) + @type) ; Handler end names (simple_handler - end_name: (identifier) @function) + end_name: + (identifier) + @function) (positional_handler - end_name: (identifier) @function) + end_name: + (identifier) + @function) (labeled_handler - end_name: (identifier) @function) + end_name: + (identifier) + @function) ; Handler parameters (parameter_list - (identifier) @variable.parameter) + (identifier) + @variable.parameter) (direct_parameter - parameter: (identifier) @variable.parameter) + parameter: + (identifier) + @variable.parameter) (labeled_parameter - parameter: (identifier) @variable.parameter) + parameter: + (identifier) + @variable.parameter) ; Property names in property statements (property_statement - name: (identifier) @property) + name: + (identifier) + @property) ; Record keys (record_entry - key: (_) @property) + key: + (_) + @property) ; Application names (application_expression - name: (string) @string.special) + name: + (string) + @string.special) (application_expression - name: (identifier) @variable) + name: + (identifier) + @variable) ; Script references (script_reference - (string) @string.special) + (string) + @string.special) ; Type casts (type_cast - type: (identifier) @type) + type: + (identifier) + @type) ; Method calls (Objective-C bridge) (method_call - method: (method_call_label) @function.method) + method: + (method_call_label) + @function.method) ; Built-in multi-word commands [ @@ -144,8 +184,8 @@ (write_text) (create_window) (create_tab) - (path_to_command) -] @function.builtin + (path_to_command)] +@function.builtin ; Multi-word property names (Terminal.app, System Events, etc.) [ @@ -165,8 +205,8 @@ (every_window) (scroll_area) (radio_group) - (radio_button) -] @variable.builtin + (radio_button)] +@variable.builtin ; Operators [ @@ -186,8 +226,8 @@ "^" "&" "div" - "mod" -] @operator + "mod"] +@operator ; Comparison operators (word-based) [ @@ -224,8 +264,8 @@ "begins with" "begin with" "ends with" - "end with" -] @operator + "end with"] +@operator ; Delimiters [ @@ -233,23 +273,31 @@ ")" "{" "}" - "," -] @punctuation.delimiter + ","] +@punctuation.delimiter ; Colon in records and labeled parameters -":" @punctuation.delimiter +":" +@punctuation.delimiter ; Property access -"'s" @punctuation.special +"'s" +@punctuation.special ; Application command highlighting (application_command - command: (identifier) @function.call) + command: + (identifier) + @function.call) ; Handler calls (handler_call - name: (identifier) @function.call) + name: + (identifier) + @function.call) ; Tell statement targets (tell_statement - target: (identifier) @variable.builtin) + target: + (identifier) + @variable.builtin) diff --git a/queries/indents.scm b/queries/indents.scm index 0ce8094..2df4f38 100644 --- a/queries/indents.scm +++ b/queries/indents.scm @@ -2,21 +2,26 @@ [ (simple_handler) (positional_handler) - (labeled_handler) -] @indent.begin + (labeled_handler)] +@indent.begin ; Indent script objects -(script_statement) @indent.begin +(script_statement) +@indent.begin ; Indent tell blocks -(tell_statement) @indent.begin +(tell_statement) +@indent.begin ; Indent if statements -(if_statement) @indent.begin +(if_statement) +@indent.begin ; Indent else/else if clauses separately -(else_if_clause) @indent.branch -(else_clause) @indent.branch +(else_if_clause) +@indent.branch +(else_clause) +@indent.branch ; Indent repeat loops [ @@ -26,27 +31,33 @@ (repeat_while) (repeat_until) (repeat_with) - (repeat_with_in) -] @indent.begin + (repeat_with_in)] +@indent.begin ; Indent try blocks -(try_statement) @indent.begin +(try_statement) +@indent.begin ; Indent considering/ignoring blocks -(considering_statement) @indent.begin -(ignoring_statement) @indent.begin +(considering_statement) +@indent.begin +(ignoring_statement) +@indent.begin ; Indent timeout blocks -(timeout_statement) @indent.begin +(timeout_statement) +@indent.begin ; Indent transaction blocks -(transaction_statement) @indent.begin +(transaction_statement) +@indent.begin ; Dedent at 'end' keywords [ "end" - "else" -] @indent.dedent + "else"] +@indent.dedent ; Comments don't affect indentation -(comment) @indent.auto +(comment) +@indent.auto diff --git a/queries/rainbows.scm b/queries/rainbows.scm index fdf67ac..143185f 100644 --- a/queries/rainbows.scm +++ b/queries/rainbows.scm @@ -4,17 +4,20 @@ [ (simple_handler) (positional_handler) - (labeled_handler) -] @rainbow.scope + (labeled_handler)] +@rainbow.scope ; Script blocks -(script_statement) @rainbow.scope +(script_statement) +@rainbow.scope ; Tell blocks -(tell_statement) @rainbow.scope +(tell_statement) +@rainbow.scope ; If statements -(if_statement) @rainbow.scope +(if_statement) +@rainbow.scope ; Repeat loops [ @@ -24,34 +27,45 @@ (repeat_while) (repeat_until) (repeat_with) - (repeat_with_in) -] @rainbow.scope + (repeat_with_in)] +@rainbow.scope ; Try blocks -(try_statement) @rainbow.scope +(try_statement) +@rainbow.scope ; Considering/Ignoring blocks -(considering_statement) @rainbow.scope -(ignoring_statement) @rainbow.scope +(considering_statement) +@rainbow.scope +(ignoring_statement) +@rainbow.scope ; Timeout blocks -(timeout_statement) @rainbow.scope +(timeout_statement) +@rainbow.scope ; Transaction blocks -(transaction_statement) @rainbow.scope +(transaction_statement) +@rainbow.scope ; Lists and records -(list) @rainbow.scope -(record) @rainbow.scope +(list) +@rainbow.scope +(record) +@rainbow.scope ; Parameter lists -(parameter_list) @rainbow.scope +(parameter_list) +@rainbow.scope ; Argument lists (handler calls) -(argument_list) @rainbow.scope +(argument_list) +@rainbow.scope ; Brackets [ - "(" ")" - "{" "}" -] @rainbow.bracket + "(" + ")" + "{" + "}"] +@rainbow.bracket diff --git a/queries/tags.scm b/queries/tags.scm index fcd4b8a..2022a18 100644 --- a/queries/tags.scm +++ b/queries/tags.scm @@ -1,29 +1,53 @@ ; Handler definitions (functions) (simple_handler - name: (identifier) @name) @definition.function + name: + (identifier) + @name) +@definition.function (positional_handler - name: (identifier) @name) @definition.function + name: + (identifier) + @name) +@definition.function (labeled_handler - name: (identifier) @name) @definition.function + name: + (identifier) + @name) +@definition.function ; Script objects (class-like structures) (script_statement - name: (identifier) @name) @definition.class + name: + (identifier) + @name) +@definition.class ; Property definitions (fields/variables) (property_statement - name: (identifier) @name) @definition.property + name: + (identifier) + @name) +@definition.property ; Application command calls (application_command - command: (identifier) @name) @reference.call + command: + (identifier) + @name) +@reference.call ; Handler calls (handler_call - name: (identifier) @name) @reference.call + name: + (identifier) + @name) +@reference.call ; Method calls (Objective-C bridge) (method_call - method: (method_call_label) @name) @reference.call + method: + (method_call_label) + @name) +@reference.call diff --git a/queries/textobjects.scm b/queries/textobjects.scm index aba0ca8..bd69310 100644 --- a/queries/textobjects.scm +++ b/queries/textobjects.scm @@ -1,28 +1,41 @@ ; Function/Handler text objects -(simple_handler) @function.around +(simple_handler) +@function.around -(positional_handler) @function.around +(positional_handler) +@function.around -(labeled_handler) @function.around +(labeled_handler) +@function.around ; Class-like text objects (script objects in AppleScript) -(script_statement) @class.around +(script_statement) +@class.around ; Parameter text objects (parameter_list - (identifier) @parameter.inside) + (identifier) + @parameter.inside) (direct_parameter - parameter: (_) @parameter.inside) + parameter: + (_) + @parameter.inside) (labeled_parameter - parameter: (_) @parameter.inside) + parameter: + (_) + @parameter.inside) ; Handler call arguments (argument_list - (_) @parameter.inside) + (_) + @parameter.inside) ; Comment text objects -(comment) @comment.inside +(comment) +@comment.inside -(comment)+ @comment.around +(comment) ++ +@comment.around diff --git a/src/grammar.json b/src/grammar.json index dc03d38..f0ab43e 100644 --- a/src/grammar.json +++ b/src/grammar.json @@ -4676,25 +4676,11 @@ } ], "conflicts": [ - [ - "repeat_times", - "expression_statement" - ], - [ - "transaction_statement", - "expression_statement" - ], - [ - "application_command", - "expression_statement" - ], - [ - "application_command" - ], - [ - "list", - "record" - ], + ["repeat_times", "expression_statement"], + ["transaction_statement", "expression_statement"], + ["application_command", "expression_statement"], + ["application_command"], + ["list", "record"], [ "direct_parameter", "labeled_parameter", @@ -4710,62 +4696,20 @@ "property_name", "_expression" ], - [ - "parameter_list", - "property_name", - "_expression" - ], - [ - "argument_list", - "parenthesized_expression" - ], - [ - "with_clause" - ], - [ - "using_clause", - "_expression" - ], - [ - "element_access", - "property_access" - ], - [ - "property_name", - "_expression" - ], - [ - "property_name" - ], - [ - "property_name", - "_identifier_or_keyword_token", - "_expression" - ], - [ - "property_name", - "_identifier_or_keyword_token" - ], - [ - "_identifier_or_keyword_token", - "script_statement" - ], - [ - "property_name", - "record_entry" - ], - [ - "on_error_clause", - "property_name", - "_expression" - ], - [ - "if_statement" - ], - [ - "if_statement", - "else_if_clause" - ] + ["parameter_list", "property_name", "_expression"], + ["argument_list", "parenthesized_expression"], + ["with_clause"], + ["using_clause", "_expression"], + ["element_access", "property_access"], + ["property_name", "_expression"], + ["property_name"], + ["property_name", "_identifier_or_keyword_token", "_expression"], + ["property_name", "_identifier_or_keyword_token"], + ["_identifier_or_keyword_token", "script_statement"], + ["property_name", "record_entry"], + ["on_error_clause", "property_name", "_expression"], + ["if_statement"], + ["if_statement", "else_if_clause"] ], "precedences": [], "externals": [ @@ -4785,4 +4729,4 @@ "inline": [], "supertypes": [], "reserved": {} -} \ No newline at end of file +} diff --git a/tree-sitter.json b/tree-sitter.json index 496fb98..c4b5cdf 100644 --- a/tree-sitter.json +++ b/tree-sitter.json @@ -6,22 +6,15 @@ "camelcase": "AppleScript", "title": "AppleScript", "scope": "source.applescript", - "file-types": [ - "applescript", - "scpt" - ], - "highlights": [ - "queries/highlights.scm" - ], - "tags": [ - "queries/tags.scm" - ], + "file-types": ["applescript", "scpt"], + "highlights": ["queries/highlights.scm"], + "tags": ["queries/tags.scm"], "injection-regex": "^applescript$", "class-name": "TreeSitterApplescript" } ], "metadata": { - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "description": "AppleScript grammar for tree-sitter", "authors": [ @@ -44,4 +37,4 @@ "swift": true, "zig": false } -} \ No newline at end of file +}