Skip to content
Merged
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
147 changes: 147 additions & 0 deletions bin/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env python3

import argparse
import json
import pathlib
import sys

import gdtoolkit.parser
from lark import Token, Tree

def load_config(input_path: pathlib.Path) -> dict | None:
config_path = input_path / ".meta/config.json"
if not config_path.exists():
return None
return json.loads((input_path / ".meta/config.json").read_text())


def solution_files(slug: str, input_path: pathlib.Path) -> list[pathlib.Path]:
config = load_config(input_path)
if config:
files = config.get("files", {}).get("solution", [])
if files:
return [input_path / file for file in files]
return [(input_path / slug.replace("-", "_")).with_suffix(".gd")]


def load_ast(slug: str, input_path: pathlib.Path) -> list[Tree]:
return [
gdtoolkit.parser.parser.parse(file.read_text())
for file in solution_files(slug, input_path)
]


def drop_docstr(trees: list[Tree]) -> list[Tree]:
def skip(t):
if isinstance(t, Tree) and t.data == "docstr_stmt":
return True
if (
isinstance(t, Tree)
and t.data == "expr_stmt"
and len(t.children) == 1
and isinstance(t.children[0], Tree)
and t.children[0].data == "expr"
and len(t.children[0].children) == 1
and isinstance(t.children[0].children[0], Tree)
and t.children[0].children[0].data == "string"
):
return True
return False

def translate(t):
if isinstance(t, Token):
return t
elif isinstance(t, Tree):
return Tree(t.data, [translate(i) for i in t.children if not skip(i)])
else:
raise ValueError(t)

return [translate(tree) for tree in trees]


def replace_func_names(trees: list[Tree]) -> tuple[list[Tree], dict[str, str]]:
varmap = {}
for tree in trees:
func_names = [
next(i.find_token("NAME"))
for i in tree.find_data(Token("RULE", "func_header"))
]
for var in func_names:
if var.value not in varmap:
varmap[var.value] = f"FUNC_{len(varmap) + 1:03d}"

def translate(t):
if isinstance(t, Token):
return Token(t.type, varmap.get(t.value, t.value))
elif isinstance(t, Tree):
return Tree(t.data, [translate(i) for i in t.children])
else:
raise ValueError(t)

revmap = {v: k for k, v in varmap.items()}

trees = [translate(tree) for tree in trees]
return trees, revmap


def replace_var_names(trees: list[Tree]) -> tuple[list[Tree], dict[str, str]]:
varmap = {}
for tree in trees:
func_vars = [
next(i.find_token("NAME"))
for token_type in ["func_var_stmt", "func_arg_typed", "func_arg_regular"]
for i in tree.find_data(Token("RULE", token_type))
]
func_vars.sort(key=lambda x: x.line)
for var in func_vars:
if var.value not in varmap:
varmap[var.value] = f"FUNCVAR_{len(varmap) + 1:03d}"

def translate(t):
if isinstance(t, Token):
return Token(t.type, varmap.get(t.value, t.value))
elif isinstance(t, Tree):
return Tree(t.data, [translate(i) for i in t.children])
else:
raise ValueError(t)

revmap = {v: k for k, v in varmap.items()}

trees = [translate(tree) for tree in trees]
return trees, revmap


def write_output(output_path: pathlib.Path, trees: list[Tree], varmap: dict[str, str]) -> None:
if not output_path.exists():
output_path.mkdir(parents=Tree, exist_ok=True)

(output_path / "representation.json").write_text(json.dumps({"version": 1}, indent=2) + "\n")
(output_path / "mapping.json").write_text(json.dumps(varmap, indent=2, sort_keys=True) + "\n")
(output_path / "representation.txt").write_text("\n\n".join(tree.pretty() for tree in trees))



def arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("slug")
parser.add_argument("input_dir")
parser.add_argument("output_dir")
return parser


def main() -> None:
args = arg_parser().parse_args()
input_path = pathlib.Path(args.input_dir)
output_path = pathlib.Path(args.output_dir)

print(f"== Creating representation for {args.slug!r} ==")
trees = load_ast(args.slug, input_path)
trees = drop_docstr(trees)
trees, varmap = replace_var_names(trees)
trees, funcmap = replace_func_names(trees)
write_output(output_path, trees, varmap | funcmap)
print(f"== Done with {args.slug!r} ==")


if __name__ == "__main__":
main()
30 changes: 2 additions & 28 deletions bin/run.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh

# Synopsis:
# Run the representer on a solution.
Expand All @@ -15,30 +15,4 @@
# Example:
# ./bin/run.sh two-fer /absolute/path/to/two-fer/solution/folder/ /absolute/path/to/output/directory/

# If any required arguments is missing, print the usage and exit
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
echo "usage: ./bin/run.sh exercise-slug /absolute/path/to/two-fer/solution/folder/ /absolute/path/to/output/directory/"
exit 1
fi

slug="$1"
input_dir="${2%/}"
output_dir="${3%/}"
representation_txt_file="${output_dir}/representation.txt"
representation_json_file="${output_dir}/representation.json"
mapping_file="${output_dir}/mapping.json"

# Create the output directory if it doesn't exist
mkdir -p "${output_dir}"

echo "${slug}: creating representation..."

# Create the representation for the solution
# TODO: replace the below commands with your own commands
# to create the representation.txt, representation.json and
# mapping.json files
echo '' > "${representation_txt_file}"
echo '{"version": 1}' > "${representation_json_file}"
echo '{}' > "${mapping_file}"

echo "${slug}: done"
python3 "${0%.*}.py" "$@"
13 changes: 13 additions & 0 deletions tests/multiple-solution-files/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"authors": [
"person"
],
"files": {
"solution": [
"one.gd",
"two.gd"
],
"test": [],
"example": []
}
}
5 changes: 4 additions & 1 deletion tests/multiple-solution-files/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
{}
{
"FUNC_001": "foo",
"FUNC_002": "bar"
}
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
{"version": 1}
{
"version": 1
}
16 changes: 16 additions & 0 deletions tests/multiple-solution-files/expected_representation.txt
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
start
func_def
func_header
FUNC_001
func_args
return_stmt
expr 1


start
func_def
func_header
FUNC_002
func_args
return_stmt
expr
string "Hello"
2 changes: 2 additions & 0 deletions tests/multiple-solution-files/one.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
func foo():
return 1
2 changes: 2 additions & 0 deletions tests/multiple-solution-files/two.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
func bar():
return "Hello"
6 changes: 5 additions & 1 deletion tests/normalize-whitespace/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
{}
{
"FUNCVAR_001": "a",
"FUNCVAR_002": "b",
"FUNC_001": "foo"
}
4 changes: 3 additions & 1 deletion tests/normalize-whitespace/expected_representation.json
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
{"version": 1}
{
"version": 1
}
22 changes: 21 additions & 1 deletion tests/normalize-whitespace/expected_representation.txt
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@

start
func_def
func_header
FUNC_001
func_args
func_var_stmt
func_var_assigned
FUNCVAR_001
expr
arith_expr
1
+
2
func_var_stmt
func_var_assigned
FUNCVAR_002
expr
arith_expr
3
+
4
5 changes: 5 additions & 0 deletions tests/normalize-whitespace/normalize_whitespace.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
func foo():
var a = 1 + 2


var b = 3 + 4
6 changes: 5 additions & 1 deletion tests/remove-comments/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
{}
{
"FUNCVAR_001": "a",
"FUNCVAR_002": "b",
"FUNC_001": "foo"
}
4 changes: 3 additions & 1 deletion tests/remove-comments/expected_representation.json
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
{"version": 1}
{
"version": 1
}
21 changes: 20 additions & 1 deletion tests/remove-comments/expected_representation.txt
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@

start
func_def
func_header
FUNC_001
func_args
func_var_stmt
func_var_assigned
FUNCVAR_001
expr
arith_expr
1
+
2
func_var_stmt
func_var_assigned
FUNCVAR_002
expr
string "Hello World"
return_stmt
expr FUNCVAR_001
10 changes: 10 additions & 0 deletions tests/remove-comments/remove_comments.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Module docstring."""
# Module comment

func foo():
"""Function docstring."""
# Function comment
var a = 1 + 2
"Some other string"
var b = "Hello World"
return a
7 changes: 7 additions & 0 deletions tests/rename-identifiers/expected_mapping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"FUNCVAR_001": "name",
"FUNCVAR_002": "suffix",
"FUNCVAR_003": "greeting",
"FUNC_001": "greet",
"FUNC_002": "greet_bob"
}
3 changes: 3 additions & 0 deletions tests/rename-identifiers/expected_representation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": 1
}
34 changes: 34 additions & 0 deletions tests/rename-identifiers/expected_representation.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
start
func_def
func_header
FUNC_001
func_args
func_arg_typed
FUNCVAR_001
str
func_arg_regular FUNCVAR_002
func_var_stmt
func_var_assigned
FUNCVAR_003
expr
string "Hello "
expr_stmt
expr
standalone_call
print
arith_expr
FUNCVAR_003
+
FUNCVAR_001
+
FUNCVAR_002
func_def
func_header
FUNC_002
func_args
expr_stmt
expr
standalone_call
FUNC_001
string "Bob"
string "!"
6 changes: 6 additions & 0 deletions tests/rename-identifiers/rename_identifiers.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
func greet(name: str, suffix):
var greeting = "Hello "
print(greeting + name + suffix)

func greet_bob():
greet("Bob", "!")
Loading