Skip to content

Commit

Permalink
chore: Improve README
Browse files Browse the repository at this point in the history
Add script to visualize parse trees.

Add example parse tree image to README.

Add example image generation to Makefile.
  • Loading branch information
reiniscirpons committed May 26, 2024
1 parent 70b53e0 commit f56a710
Show file tree
Hide file tree
Showing 6 changed files with 439 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build
temp*/
log.html
examples/corpus_*.tar.gz
*.egg-info
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ test_pkg: $(EXAMPLES_DIR)/temp_corpus_pkg_$(CORPUS_VERSION) compile
test_all: test_quick $(EXAMPLES_DIR)/temp_corpus_gap_$(CORPUS_VERSION) $(EXAMPLES_DIR)/temp_corpus_pkg_$(CORPUS_VERSION)
tree-sitter parse '$(EXAMPLES_DIR)/temp_corpus_*/*.g*' --quiet --stat

image-example-parse.svg: grammar.js src/scanner.c ./etc/visualize_parse_tree.py
echo 'G := Group((1, 2, 3), (1, 2)(3, 4)); IsNormal(SymmetricGroup(4), G);' | ./etc/visualize_parse_tree.py -o ./image-example-parse.svg

clean:
rm -rf $(EXAMPLES_DIR)/temp_*
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@

[tree-sitter](https://github.com/tree-sitter/tree-sitter) grammar for [GAP system](https://www.gap-system.org/) files.

## Want to help complete this?
## Example

![Example of a parse tree generated with `tree-sitter-gap`](image-example-parse.svg)

The above is a parse tree generated using the `tree-sitter-gap` grammar for the following code snippet:

```gap
G := Group((1, 2, 3), (1, 2)(3, 4));
IsNormal(SymmetricGroup(4), G);
```

## Want to help improve this?

- Install `tree-sitter` (version >= 0.22.2), [official instructions](https://tree-sitter.github.io/tree-sitter/creating-parsers#installation);
- Read ["how to create a parser"](https://tree-sitter.github.io/tree-sitter/creating-parsers);
- Make the existing tests pass;
- Resolve the TODOs in source and test files;
- Add more missing language features;
- Validate by running on the whole `GAP` library and on packages, see [Tests](#tests) section below.
Expand Down
4 changes: 2 additions & 2 deletions etc/extract_g.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
are actually .tst files and files that are passed as input to `ReadAsFunction`.
"""

import argparse


def is_tst_file(lines: list[str]) -> bool:
"""Check if lines correspond to a `tst` file.
Expand Down Expand Up @@ -94,8 +96,6 @@ def extract_g_lines_from_tst_lines(lines: list[str]) -> list[str]:
return result_lines


import argparse

if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Extract or fixup GAP code from a .g or .tst file."
Expand Down
95 changes: 95 additions & 0 deletions etc/visualize_parse_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""Script to visualize parse trees generated by `tree-sitter-gap`
Make sure to run `python3 -m pip install .` in the project root to install the
python bindings for the development version of the `tree-sitter-gap` grammar.
"""

import argparse
import tree_sitter_gap as tsgap
from tree_sitter import Language, Parser, Tree, Node
import pydot


def traverse_tree(tree: Tree):
cursor = tree.walk()

nodes: list[Node] = []
idx_of: dict[Node, int] = {}
visited_children = False
while True:
if not visited_children:
node = cursor.node
assert node is not None
idx_of[node] = len(nodes)
nodes.append(node)
if not cursor.goto_first_child():
visited_children = True
elif cursor.goto_next_sibling():
visited_children = False
elif not cursor.goto_parent():
break

return nodes, idx_of


if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Visualize the parse tree of a file, outputs svg"
)
parser.add_argument(
"-i",
"--in_file",
type=str,
default=None,
help="Name of the file to process. If omitted, input is taken from stdin instead.",
required=False,
)
parser.add_argument(
"-o",
"--out_file",
type=str,
help="Name of the output file.",
)
args = parser.parse_args()

GAP_LANGUAGE = Language(tsgap.language())
parser = Parser(GAP_LANGUAGE)

if args.in_file is None:
text = input().encode("utf-8")
else:
with open(args.in_file, "rb") as in_file:
text = in_file.read()

tree = parser.parse(text)
nodes, idx_of = traverse_tree(tree)

dot = pydot.Dot(graph_name="D", graph_type="digraph")
for node_idx, node in enumerate(nodes):
if not node.is_named:
continue
else:
dot.add_node(
pydot.Node(
f"node_{node_idx}",
label=node.type,
)
)

for node_idx, node in enumerate(nodes):
for relative_idx, child in enumerate(node.children):
child_idx = idx_of[child]
if not node.is_named or not child.is_named:
continue
field = node.field_name_for_child(relative_idx)
if field is None:
dot.add_edge(pydot.Edge(f"node_{node_idx}", f"node_{child_idx}"))
else:
dot.add_edge(
pydot.Edge(
f"node_{node_idx}", f"node_{child_idx}", label=f"{field}"
)
)

dot.write_svg(args.out_file, prog="dot")
Loading

0 comments on commit f56a710

Please sign in to comment.