Start working on emacs mode generation

This commit is contained in:
John Doty 2024-09-06 10:20:34 -07:00
parent 501c2e3fbe
commit 23981f82ce

120
parser/emacs.py Normal file
View file

@ -0,0 +1,120 @@
# https://www.masteringemacs.org/article/lets-write-a-treesitter-major-mode
import dataclasses
import itertools
import pathlib
import textwrap
from parser.tree_sitter import terminal_name
from . import parser
@dataclasses.dataclass(frozen=True, order=True)
class FaceQuery:
feature: str # Important to be first!
face: str
node: str
field: str | None
def gather_faces(grammar: parser.Grammar):
nts = {nt.name: nt for nt in grammar.non_terminals()}
def scoop(node: str, input: parser.FlattenedWithMetadata, visited: set[str]) -> list[FaceQuery]:
parts = []
for item in input:
if isinstance(item, tuple):
meta, sub = item
parts.extend(scoop(node, sub, visited))
highlight = meta.get("highlight")
if isinstance(highlight, parser.HighlightMeta):
field_name = meta.get("field")
if not isinstance(field_name, str):
raise Exception("Highlight must come with a field name") # TODO
feature = highlight.font_lock_feature
face = highlight.font_lock_face
if feature and face:
parts.append(
FaceQuery(
node=node,
field=field_name,
feature=feature,
face=face,
)
)
elif isinstance(item, str):
nt = nts[item]
if nt.transparent:
if nt.name in visited:
continue
visited.add(nt.name)
body = nt.fn(grammar)
for production in body.flatten(with_metadata=True):
parts.extend(scoop(node, production, visited))
return parts
queries: list[FaceQuery] = []
for rule in grammar.non_terminals():
if rule.transparent:
continue
body = rule.fn(grammar)
for production in body.flatten(with_metadata=True):
queries.extend(scoop(rule.name, production, set()))
for rule in grammar.terminals():
highlight = rule.meta.get("highlight")
if isinstance(highlight, parser.HighlightMeta):
feature = highlight.font_lock_feature
face = highlight.font_lock_face
if feature and face:
queries.append(
FaceQuery(
node=terminal_name(rule),
field=None,
feature=feature,
face=face,
)
)
# Remove duplicates, which happen.
queries = list(set(queries))
queries.sort()
# Group by feature.
features = []
for feature, qs in itertools.groupby(queries, key=lambda x: x.feature):
feature_group = f":language {grammar.name}\n:override t\n:feature {feature}\n"
face_queries = []
for query in qs:
if query.field:
fq = f"({query.node} {query.field}: _ @{query.face})"
else:
fq = f"({query.node}) @{query.face}"
face_queries.append(fq)
face_queries_str = "\n ".join(face_queries)
feature_group += f"({face_queries_str})\n"
features.append(feature_group)
feature_string = "\n".join(features)
feature_string = textwrap.indent(feature_string, " ")
feature_string = feature_string.strip()
feature_string = f"""
(defvar {grammar.name}-font-lock-rules
'({feature_string})
"Tree-sitter font lock rules for {grammar.name}.")
""".strip()
return feature_string
def emit_emacs_major_mode(grammar: parser.Grammar, path: pathlib.Path | str):
face_var = gather_faces(grammar)