Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions integration/textmate/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load("@rules_python//python:py_test.bzl", "py_test")

package(default_visibility = ["//visibility:public"])

exports_files(["emboss.tmLanguage.json"])

py_test(
name = "textmate_test",
srcs = ["textmate_test.py"],
data = ["emboss.tmLanguage.json"],
)
10 changes: 10 additions & 0 deletions integration/textmate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# TextMate Grammar for Emboss

This directory contains the TextMate grammar (`emboss.tmLanguage.json`) for the Emboss language.

It provides syntax highlighting for tools that support TextMate grammars, such as:
- VS Code (via extensions)
- GitHub (via Linguist)
- Sublime Text

To add Emboss syntax highlighting to GitHub, this file can be used by GitHub's [Linguist](https://github.com/github-linguist/linguist) library.
160 changes: 160 additions & 0 deletions integration/textmate/emboss.tmLanguage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
{
"name": "Emboss",
"scopeName": "source.emboss",
"fileTypes": [
"emb"
],
"patterns": [
{
"include": "#strings"
},
{
"include": "#comments"
},
{
"include": "#doc-comments"
},
Comment on lines +8 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In the patterns array, #comments and #doc-comments are included before #strings. This causes any # or -- character inside a double-quoted string literal to be incorrectly highlighted as a comment. Moving #strings to the top of the patterns list ensures that string literals are matched first.

Suggested change
{
"include": "#comments"
},
{
"include": "#doc-comments"
},
{
"include": "#strings"
},
{
"include": "#strings"
},
{
"include": "#comments"
},
{
"include": "#doc-comments"
},

{
"include": "#keywords"
},
{
"include": "#builtins"
},
{
"include": "#types"
},
{
"include": "#constants"
},
{
"include": "#numbers"
},
{
"include": "#operators"
},
{
"include": "#punctuation"
}
Comment on lines +31 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure that the new punctuation patterns are applied during syntax highlighting, we should include the #punctuation pattern in the main patterns array.

    {
      "include": "#operators"
    },
    {
      "include": "#punctuation"
    }
  ],

],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.line.number-sign.emboss",
"match": "#.*$"
}
]
},
"doc-comments": {
"patterns": [
{
"name": "comment.line.double-dash.documentation.emboss",
"match": "--(?: .*|)$"
},
{
"name": "invalid.illegal.documentation.emboss",
"match": "--[^ ].*$"
}
]
},
"strings": {
"patterns": [
{
"name": "string.quoted.double.emboss",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "constant.character.escape.emboss",
"match": "\\\\[n\\\\\"]"
}
]
}
]
},
"keywords": {
"patterns": [
{
"name": "keyword.control.emboss",
"match": "\\b(struct|enum|bits|external|import|as|if|let)\\b"
},
{
Comment on lines +78 to +81

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The keyword union is included in the control keywords list. However, Emboss does not support union (only struct, bits, and enum are valid container types). Including it here will incorrectly highlight union as a keyword when it is actually treated as a regular identifier (or SnakeWord) by the compiler.

        {
          "name": "keyword.control.emboss",
          "match": "\\b(struct|enum|bits|external|import|as|if|let)\\b"
        },

"name": "constant.language.emboss",
"match": "\\b(true|false)\\b"
}
]
},
"builtins": {
"patterns": [
{
"name": "variable.language.emboss",
"match": "(?<!\\w)(\\$default|\\$static_size_in_bits|\\$is_statically_sized|\\$next|\\$max|\\$present|\\$upper_bound|\\$lower_bound)\\b"
},
{
Comment on lines +90 to +93

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The builtins list includes $reserved and this. Neither of these are valid builtins or keywords in the Emboss language. Any word starting with $ that is not explicitly in the compiler's LITERAL_TOKEN_PATTERNS (like $reserved) is tokenized as a BadWord and rejected. Similarly, this is treated as a regular identifier. They should be removed from the builtins list.

        {
          "name": "variable.language.emboss",
          "match": "(?<!\\w)(\\$default|\\$static_size_in_bits|\\$is_statically_sized|\\$next|\\$max|\\$present|\\$upper_bound|\\$lower_bound)\\b"
        },

"name": "variable.other.emboss",
"match": "(?<!\\w)(\\$size_in_bits|\\$size_in_bytes|\\$max_size_in_bits|\\$max_size_in_bytes|\\$min_size_in_bits|\\$min_size_in_bytes)\\b"
}
]
},
"types": {
"patterns": [
{
"name": "entity.name.type.emboss",
"match": "\\b[A-Z][a-zA-Z0-9]*[a-z][a-zA-Z0-9]*\\b"
}
]
},
"constants": {
"patterns": [
{
"name": "variable.other.constant.emboss",
"match": "\\b[A-Z][A-Z_0-9]*[A-Z_][A-Z_0-9]*\\b"
}
]
},
"numbers": {
"patterns": [
{
"name": "constant.numeric.hex.emboss",
"match": "\\b0[xX](?:[0-9a-fA-F]+|_?[0-9a-fA-F]{1,4}(?:_[0-9a-fA-F]{4})*|_?[0-9a-fA-F]{1,8}(?:_[0-9a-fA-F]{8})*)\\b"
},
{
"name": "constant.numeric.binary.emboss",
"match": "\\b0[bB](?:[01]+|_?[01]{1,4}(?:_[01]{4})*|_?[01]{1,8}(?:_[01]{8})*)\\b"
},
{
"name": "constant.numeric.decimal.emboss",
"match": "\\b(?:\\d+|\\d{1,3}(?:_\\d{3})+)\\b"
}
Comment on lines +118 to +128

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The number regexes do not fully align with the Emboss compiler's tokenizer:

  1. Hex and binary numbers can have an optional underscore right after the prefix (e.g., 0x_1234, 0b_0101), which the current regexes do not support.
  2. Decimal numbers only allow underscores when grouping digits in threes (e.g., 1_000_000). The current regex \\b\\d+(_\\d+)*\\b incorrectly matches invalid formats like 12_34 (which the compiler rejects as BadNumber).

Updating these regexes ensures accurate syntax highlighting that matches the compiler's behavior.

Suggested change
"name": "constant.numeric.hex.emboss",
"match": "\\b0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*\\b"
},
{
"name": "constant.numeric.binary.emboss",
"match": "\\b0[bB][01]+(_[01]+)*\\b"
},
{
"name": "constant.numeric.decimal.emboss",
"match": "\\b\\d+(_\\d+)*\\b"
}
"name": "constant.numeric.hex.emboss",
"match": "\\b0[xX](?:[0-9a-fA-F]+|_?[0-9a-fA-F]{1,4}(?:_[0-9a-fA-F]{4})*|_?[0-9a-fA-F]{1,8}(?:_[0-9a-fA-F]{8})*)\\b"
},
{
"name": "constant.numeric.binary.emboss",
"match": "\\b0[bB](?:[01]+|_?[01]{1,4}(?:_[01]{4})*|_?[01]{1,8}(?:_[01]{8})*)\\b"
},
{
"name": "constant.numeric.decimal.emboss",
"match": "\\b(?:\\d+|\\d{1,3}(?:_\\d{3})+)\\b"

]
},
"operators": {
"patterns": [
{
"name": "keyword.operator.emboss",
"match": "==|!=|<=|>=|<|>|&&|\\|\\||\\+|-|\\*|\\?|="
}
]
},
"punctuation": {
"patterns": [
{
"name": "punctuation.separator.emboss",
"match": "[:,]"
},
{
"name": "punctuation.section.brackets.emboss",
"match": "\\[|\\]"
},
{
"name": "punctuation.section.parens.emboss",
"match": "\\(|\\)"
},
{
"name": "punctuation.accessor.emboss",
"match": "\\."
}
Comment on lines +134 to +156

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The operators pattern includes / as an operator. However, Emboss does not support division, and / is not a valid token in the Emboss language. It should be removed.

Suggested change
"name": "keyword.operator.emboss",
"match": "==|!=|<=|>=|<|>|&&|\\|\\||\\+|-|\\*|/"
}
"name": "keyword.operator.emboss",
"match": "==|!=|<=|>=|<|>|&&|\\|\\||\\+|-|\\*"
}

]
}
Comment on lines +131 to +158

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Adding explicit highlighting for punctuation (such as colons, commas, brackets, parentheses, and member access dots) will significantly improve the readability and visual structure of Emboss code in editors like VS Code and GitHub.

    "operators": {
      "patterns": [
        {
          "name": "keyword.operator.emboss",
          "match": "==|!=|<=|>=|<|>|&&|\\|\\||\\+|-|\\*|="
        }
      ]
    },
    "punctuation": {
      "patterns": [
        {
          "name": "punctuation.separator.emboss",
          "match": "[:|,]"
        },
        {
          "name": "punctuation.section.brackets.emboss",
          "match": "\\\\[|\\\\]"
        },
        {
          "name": "punctuation.section.parens.emboss",
          "match": "\\\\(|\\\\)"
        },
        {
          "name": "punctuation.accessor.emboss",
          "match": "\\\\."
        }
      ]
    }

}
}
57 changes: 57 additions & 0 deletions integration/textmate/textmate_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import os
import re
import unittest


class TextmateTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
json_path = os.path.join(os.path.dirname(__file__), "emboss.tmLanguage.json")
with open(json_path, encoding="utf-8") as f:
cls.data = json.load(f)

def test_valid_json(self):
self.assertEqual(self.data.get("name"), "Emboss")
self.assertEqual(self.data.get("scopeName"), "source.emboss")
self.assertIn("emb", self.data.get("fileTypes", []))

def test_all_includes_exist_and_regexes_compile(self):
repository = self.data.get("repository", {})

def check_pattern(pattern):
if "include" in pattern:
include = pattern["include"]
if include.startswith("#"):
self.assertIn(include[1:], repository)
for regex_key in ("match", "begin", "end"):
if regex_key in pattern:
re.compile(pattern[regex_key])
if "patterns" in pattern:
for p in pattern["patterns"]:
check_pattern(p)

if "patterns" in self.data:
for p in self.data["patterns"]:
check_pattern(p)
for val in repository.values():
check_pattern(val)


if __name__ == "__main__":
unittest.main()
Loading