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
Empty file.
13 changes: 13 additions & 0 deletions scratchattach/editor/code_translation/example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## For testing. The file extension is temporary and might change in the future. ##

WHEN (green_flag_clicked) {
until {
say()say()
}
sa()
}
custom_block hello_%s_ (<a>, awd) {
until (1) {
say((8 + 2) * (6 - 3))
}
}
56 changes: 56 additions & 0 deletions scratchattach/editor/code_translation/language.lark
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
start: top_level_block*

top_level_block: hat "{" block* "}"
| PREPROC_INSTR
| COMMMENT

PREPROC_INSTR: "%%" _PREPROC_INSTR_CONTENT "%%"

_PREPROC_INSTR_CONTENT: /[\s\S]+?/

COMMMENT: "##" _COMMMENT_CONTENT "##"

_COMMMENT_CONTENT: /[\s\S]+?/

hat: [event_hat | block_hat]

event_hat: "when"i "(" EVENT ")"
block_hat: "custom_block"i BLOCK_NAME "(" [ param ("," param)* ] ")"

param: value_param
| bool_param
value_param: PARAM_NAME
bool_param: "<" PARAM_NAME ">"

EVENT: "green_flag_clicked"i

block: CONTROL_BLOCK_NAME ["(" block_params ")"] "{" block_content "}"
| BLOCK_NAME ["(" block_params ")"] [";" | "\n" | " "]

block_params: expr*
block_content: block*

expr: addition | subtraction | multiplication | division | LITERAL_NUMBER | "(" expr ")"

low_expr1: ("(" expr ")") | LITERAL_NUMBER
low_expr2: low_expr1 | multiplication | division

addition: expr "+" expr
subtraction: expr "-" low_expr2
multiplication: low_expr2 "*" low_expr2
division: low_expr2 "/" low_expr1

CONTROL_BLOCK_NAME: "repeat"i
| "until"i
| "forever"i

PARAM_NAME: ("a".."z" | "A".."Z" | "_" | "-" | "%" | "+")+
BLOCK_NAME: [MODULE_NAME "."] ("a".."z" | "A".."Z" | "_" | "-" | "%" | "+")+

MODULE_NAME: "params"i
| "vars"i
| "lists"i

WS: /\s+/
%ignore WS
%import common.SIGNED_NUMBER -> LITERAL_NUMBER
170 changes: 170 additions & 0 deletions scratchattach/editor/code_translation/parse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
from __future__ import annotations
from pathlib import Path
from typing import Union, Generic, TypeVar
from abc import ABC, abstractmethod
from collections.abc import Sequence

from lark import Lark, Transformer, Tree, Token, v_args
from lark.reconstruct import Reconstructor

R = TypeVar("R")
class SupportsRead(ABC, Generic[R]):
@abstractmethod
def read(self, size: int | None = -1) -> R:
pass

LANG_PATH = Path(__file__).parent / "language.lark"

lang = Lark(LANG_PATH.read_text(), maybe_placeholders=False)
reconstructor = Reconstructor(lang)

def parse(script: Union[str, bytes, SupportsRead[str], Path]) -> Tree:
if isinstance(script, Path):
script = script.read_text()
if isinstance(script, SupportsRead):
read_data = script.read()
assert isinstance(read_data, str)
script = read_data
if isinstance(script, bytes):
script = script.decode("utf-8")
return lang.parse(script)

def unparse(tree: Tree) -> str:
return reconstructor.reconstruct(tree)

class PrettyUnparser(Transformer):
INDENT_STRING = " "

@classmethod
def _indent(cls, text):
if not text:
return ""
return "\n".join(cls.INDENT_STRING + line for line in text.splitlines())

def PARAM_NAME(self, token):
return token.value

def BLOCK_NAME(self, token):
return token.value

def EVENT(self, token):
return token.value

def CONTROL_BLOCK_NAME(self, token):
return token.value

def _PREPROC_INSTR_CONTENT(self, token):
return token.value

def _COMMMENT_CONTENT(self, token):
return token.value

@v_args(inline=True)
def hat(self, child):
return child

@v_args(inline=True)
def param(self, child):
return child

@v_args(inline=True)
def value_param(self, name):
return name

@v_args(inline=True)
def bool_param(self, name):
return f"<{name}>"

@v_args(inline=True)
def event_hat(self, event_name):
return f"when ({event_name})"

def block_hat(self, items):
name, *params = items
params_str = ", ".join(params)
return f"custom_block {name} ({params_str})"

@v_args(inline=True)
def PREPROC_INSTR(self, content):
return f"%%{content}%%"

@v_args(inline=True)
def COMMMENT(self, content):
return f"##{content}##"

def block(self, items):
params = []
inner_blocks = []
for i in items[1:]:
if not isinstance(i, Tree):
continue
if str(i.data) == "block_content":
inner_blocks.extend(i.children)
if str(i.data) == "block_params":
params.extend(i.children)
block_name = items[0]
block_text = f"{block_name}({', '.join(params)})" if params or not inner_blocks else f"{block_name}"
if len(inner_blocks) > 0:
blocks_content = "\n".join(inner_blocks)
indented_content = self._indent(blocks_content)
block_text += f" {{\n{indented_content}\n}}"
return block_text

def LITERAL_NUMBER(self, number: str):
return number

@v_args(inline=True)
def expr(self, item):
return item

@v_args(inline=True)
def low_expr1(self, item):
if " " in item:
return f"({item})"
return item

@v_args(inline=True)
def low_expr2(self, item):
return item

def addition(self, items):
return items[0] + " + " + items[1]

def subtraction(self, items):
return items[0] + " - " + items[1]

def multiplication(self, items):
return items[0] + " * " + items[1]

def division(self, items):
return items[0] + " / " + items[1]

def top_level_block(self, items):
first_item = items[0]
if first_item.startswith("%%") or first_item.startswith("##"):
return first_item

hat, *blocks = items
blocks_content = "\n".join(blocks)
indented_content = self._indent(blocks_content)
return f"{hat} {{\n{indented_content}\n}}"

def start(self, items):
return "\n\n".join(items)

def pretty_unparse(tree: Tree):
return PrettyUnparser().transform(tree)

if __name__ == "__main__":
EXAMPLE_FILE = Path(__file__).parent / "example.txt"
tree = parse(EXAMPLE_FILE)
print(tree.pretty())
print()
print()
print(tree)
print()
print()
print(unparse(tree))
print()
print()
print(pretty_unparse(tree))
1 change: 1 addition & 0 deletions scratchattach/site/classroom.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from . import user, activity
from ._base import BaseSiteComponent
from scratchattach.utils import exceptions, commons
from scratchattach.utils.commons import headers


@dataclass
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
long_description=LONG_DESCRIPTION,
packages=find_packages(),
install_requires=requirements,
extras_require={
"lark": ["lark"]
},
keywords=['scratch api', 'scratchattach', 'scratch api python', 'scratch python', 'scratch for python', 'scratch', 'scratch cloud', 'scratch cloud variables', 'scratch bot'],
url='https://scratchattach.tim1de.net',
classifiers=[
Expand Down