typescratch 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- typescratch/__init__.py +146 -0
- typescratch/__main__.py +10 -0
- typescratch/assets.py +69 -0
- typescratch/ast_nodes.py +363 -0
- typescratch/blocks.py +961 -0
- typescratch/cli.py +67 -0
- typescratch/codegen.py +1631 -0
- typescratch/errors.py +94 -0
- typescratch/lexer.py +302 -0
- typescratch/parser.py +1022 -0
- typescratch/sb3.py +60 -0
- typescratch/scratchcat_official.svg +42 -0
- typescratch-1.0.0.dist-info/METADATA +654 -0
- typescratch-1.0.0.dist-info/RECORD +18 -0
- typescratch-1.0.0.dist-info/WHEEL +5 -0
- typescratch-1.0.0.dist-info/entry_points.txt +2 -0
- typescratch-1.0.0.dist-info/licenses/LICENSE +201 -0
- typescratch-1.0.0.dist-info/top_level.txt +1 -0
typescratch/__init__.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""TypeScratch - a small programming language that compiles to Scratch 3 (.sb3) files.
|
|
2
|
+
|
|
3
|
+
Library usage:
|
|
4
|
+
|
|
5
|
+
import typescratch
|
|
6
|
+
|
|
7
|
+
# Compile a .tysh file to a .sb3 next to it
|
|
8
|
+
typescratch.file("C:/Users/pc/Desktop/thing.tysh")
|
|
9
|
+
|
|
10
|
+
# Or specify the output path explicitly
|
|
11
|
+
typescratch.file("thing.tysh", out="thing.sb3")
|
|
12
|
+
|
|
13
|
+
# Compile from a source string
|
|
14
|
+
src = '''
|
|
15
|
+
s "Sprite1"
|
|
16
|
+
when gf clicked {
|
|
17
|
+
say(Hello, World!)(2)
|
|
18
|
+
}
|
|
19
|
+
'''
|
|
20
|
+
sb3_bytes = typescratch.source(src) # returns bytes
|
|
21
|
+
typescratch.source(src, out="out.sb3") # writes to file
|
|
22
|
+
|
|
23
|
+
# Lower-level: compile and return (project_json, assets_dict)
|
|
24
|
+
project, assets = typescratch.compile_source(src)
|
|
25
|
+
|
|
26
|
+
CLI usage:
|
|
27
|
+
|
|
28
|
+
typescratch build thing.tysh # writes thing.sb3
|
|
29
|
+
typescratch build thing.tysh --out out.sb3
|
|
30
|
+
typescratch build thing.tysh --debug # verbose AST/block dump
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import os
|
|
36
|
+
import sys
|
|
37
|
+
from typing import Optional, Tuple, Dict, Any
|
|
38
|
+
|
|
39
|
+
from .errors import CompileError, Debug
|
|
40
|
+
from .lexer import tokenize
|
|
41
|
+
from .parser import Parser
|
|
42
|
+
from .codegen import Codegen
|
|
43
|
+
from .sb3 import write_sb3, write_sb3_bytes
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__version__ = "1.0.0"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def compile_source(src: str, filename: str = "<tysh>", debug: bool = False,
|
|
50
|
+
asset_root: Optional[str] = None
|
|
51
|
+
) -> Tuple[dict, Dict[str, bytes]]:
|
|
52
|
+
"""Compile a TypeScratch source string to (project_json, assets_dict).
|
|
53
|
+
|
|
54
|
+
`asset_root` is the base directory for resolving relative `img=` paths.
|
|
55
|
+
If None, defaults to the directory of `filename` (or cwd).
|
|
56
|
+
"""
|
|
57
|
+
dbg = Debug(enabled=debug)
|
|
58
|
+
dbg.section("Tokens")
|
|
59
|
+
toks = tokenize(src, filename)
|
|
60
|
+
for t in toks:
|
|
61
|
+
dbg.line(f"{t.line:>4}:{t.col:<3} {t.type:<14} {t.value!r}")
|
|
62
|
+
|
|
63
|
+
dbg.section("AST")
|
|
64
|
+
program = Parser(toks, filename).parse_program()
|
|
65
|
+
dbg.line(repr(program))
|
|
66
|
+
for tgt in program.targets:
|
|
67
|
+
dbg.line(f" target {tgt.kind} {tgt.name!r} scripts={len(tgt.scripts)} "
|
|
68
|
+
f"custom={len(tgt.custom_blocks)} costumes={len(tgt.costumes)}")
|
|
69
|
+
for s in tgt.scripts:
|
|
70
|
+
dbg.line(f" hat={s.hat.kind} body={len(s.body)} stmts")
|
|
71
|
+
|
|
72
|
+
if asset_root is None:
|
|
73
|
+
if filename and filename != "<tysh>":
|
|
74
|
+
asset_root = os.path.dirname(os.path.abspath(filename))
|
|
75
|
+
else:
|
|
76
|
+
asset_root = os.getcwd()
|
|
77
|
+
|
|
78
|
+
cg = Codegen(program, debug=dbg, asset_root=asset_root)
|
|
79
|
+
project, assets = cg.generate()
|
|
80
|
+
return project, assets
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def source(src: str, out: Optional[str] = None, debug: bool = False,
|
|
84
|
+
filename: str = "<tysh>") -> bytes:
|
|
85
|
+
"""Compile a TypeScratch source string and return the .sb3 bytes.
|
|
86
|
+
|
|
87
|
+
If `out` is given, also write to that file path.
|
|
88
|
+
"""
|
|
89
|
+
project, assets = compile_source(src, filename=filename, debug=debug)
|
|
90
|
+
sb3_bytes = write_sb3_bytes(project, assets)
|
|
91
|
+
if out:
|
|
92
|
+
with open(out, "wb") as f:
|
|
93
|
+
f.write(sb3_bytes)
|
|
94
|
+
return sb3_bytes
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def file(path: str, out: Optional[str] = None, debug: bool = False) -> str:
|
|
98
|
+
"""Compile a .tysh file to a .sb3 file.
|
|
99
|
+
|
|
100
|
+
Returns the output file path.
|
|
101
|
+
"""
|
|
102
|
+
if not os.path.isfile(path):
|
|
103
|
+
raise FileNotFoundError(f"TypeScratch source file not found: {path}")
|
|
104
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
105
|
+
src = f.read()
|
|
106
|
+
if out is None:
|
|
107
|
+
# default: same name, .sb3 extension, next to input
|
|
108
|
+
base, _ = os.path.splitext(path)
|
|
109
|
+
out = base + ".sb3"
|
|
110
|
+
source(src, out=out, debug=debug, filename=path)
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Friendly alias - `typescratch.compile("out.sb3")` after `typescratch.file("in.tysh")`
|
|
115
|
+
# Actually the user wanted `typescratch.file(...)` then `typescratch.compile(...)`.
|
|
116
|
+
# Let me support both styles:
|
|
117
|
+
|
|
118
|
+
def compile(out_path: str, src_path: Optional[str] = None, debug: bool = False) -> str:
|
|
119
|
+
"""Compile a .tysh source to a specific .sb3 output path.
|
|
120
|
+
|
|
121
|
+
If `src_path` is None, looks for a .tysh file with the same basename
|
|
122
|
+
as `out_path` (e.g. for `out.sb3`, looks for `out.tysh` in the same dir).
|
|
123
|
+
"""
|
|
124
|
+
if src_path is None:
|
|
125
|
+
base, _ = os.path.splitext(out_path)
|
|
126
|
+
for ext in (".tysh", ".ts", ".tsh"):
|
|
127
|
+
candidate = base + ext
|
|
128
|
+
if os.path.isfile(candidate):
|
|
129
|
+
src_path = candidate
|
|
130
|
+
break
|
|
131
|
+
if src_path is None:
|
|
132
|
+
raise FileNotFoundError(
|
|
133
|
+
f"no source file found for output {out_path!r} "
|
|
134
|
+
f"(looked for {base}.tysh)"
|
|
135
|
+
)
|
|
136
|
+
return file(src_path, out=out_path, debug=debug)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
__all__ = [
|
|
140
|
+
"file",
|
|
141
|
+
"source",
|
|
142
|
+
"compile",
|
|
143
|
+
"compile_source",
|
|
144
|
+
"CompileError",
|
|
145
|
+
"__version__",
|
|
146
|
+
]
|
typescratch/__main__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Allow running TypeScratch as `python -m typescratch build ...`.
|
|
2
|
+
|
|
3
|
+
This file is executed when you run `python -m typescratch` — it delegates
|
|
4
|
+
to the CLI's main function.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .cli import main
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
raise SystemExit(main())
|
typescratch/assets.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Default Scratch assets used when a sprite/backdrop has no image.
|
|
2
|
+
|
|
3
|
+
We embed the OFFICIAL Scratch Cat costume SVG (costume1 from the
|
|
4
|
+
default Scratch 3.0 project) - extracted from an actual .sb3 file -
|
|
5
|
+
so that any project we generate looks correct in the Scratch editor.
|
|
6
|
+
|
|
7
|
+
A blank white backdrop SVG is also provided.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
# Path to the official Scratch Cat SVG (sitting alongside this module)
|
|
13
|
+
_CAT_SVG_PATH = os.path.join(os.path.dirname(__file__), "scratchcat_official.svg")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _load_official_cat() -> bytes:
|
|
17
|
+
"""Load the official Scratch Cat SVG from disk."""
|
|
18
|
+
try:
|
|
19
|
+
with open(_CAT_SVG_PATH, "rb") as f:
|
|
20
|
+
return f.read()
|
|
21
|
+
except FileNotFoundError:
|
|
22
|
+
# Fallback to a placeholder if the SVG file is missing (e.g. if
|
|
23
|
+
# the package was installed without the data file)
|
|
24
|
+
return PLACEHOLDER_CAT_SVG.encode("utf-8")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Official Scratch Cat - the real deal, loaded from scratchcat_official.svg
|
|
28
|
+
# Rotation center: (48, 50) - matches Scratch's default.
|
|
29
|
+
SCRATCH_CAT_SVG = _load_official_cat().decode("utf-8", errors="replace")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# A simple blank white backdrop, the size of a default Scratch stage.
|
|
33
|
+
BLANK_BACKDROP_SVG = """<?xml version="1.0" encoding="UTF-8"?>
|
|
34
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="360" viewBox="0 0 480 360">
|
|
35
|
+
<rect width="480" height="360" fill="#ffffff"/>
|
|
36
|
+
</svg>
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# A neutral grey costume used as a last-resort fallback if the official
|
|
40
|
+
# SVG file is missing from the install.
|
|
41
|
+
PLACEHOLDER_CAT_SVG = """<?xml version="1.0" encoding="UTF-8"?>
|
|
42
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
|
|
43
|
+
<rect width="96" height="96" fill="#cccccc"/>
|
|
44
|
+
<text x="48" y="50" text-anchor="middle" font-family="sans-serif" font-size="14">cat</text>
|
|
45
|
+
</svg>
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# A short silent WAV used when a sound is referenced but no file exists.
|
|
49
|
+
SILENT_WAV = (
|
|
50
|
+
b"RIFF\x24\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00"
|
|
51
|
+
b"\x44\xac\x00\x00\x88\x58\x01\x00\x02\x00\x10\x00data\x00\x00\x00\x00"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def default_sprite_costume():
|
|
56
|
+
"""Return (name, svg_bytes, md5ext, rotation_center_x, rotation_center_y)
|
|
57
|
+
for the official Scratch Cat."""
|
|
58
|
+
data = SCRATCH_CAT_SVG.encode("utf-8")
|
|
59
|
+
return ("costume1", data, "scratchcat.svg", 48, 50)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def default_backdrop():
|
|
63
|
+
"""Return (name, svg_bytes, md5ext, rotation_center_x, rotation_center_y)
|
|
64
|
+
for a blank white backdrop."""
|
|
65
|
+
return ("backdrop1", BLANK_BACKDROP_SVG.encode("utf-8"), "backdrop1.svg", 240, 180)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def placeholder_costume(name="costume1"):
|
|
69
|
+
return (name, PLACEHOLDER_CAT_SVG.encode("utf-8"), "placeholder.svg", 30, 30)
|
typescratch/ast_nodes.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""AST node definitions for TypeScratch.
|
|
2
|
+
|
|
3
|
+
The grammar (informally):
|
|
4
|
+
|
|
5
|
+
program := (extension | target)*
|
|
6
|
+
|
|
7
|
+
extension := 'extension' EXT_NAME
|
|
8
|
+
target := sprite | backdrop
|
|
9
|
+
sprite := 's' STRING [sprite_attrs]
|
|
10
|
+
backdrop := 'b' STRING [backdrop_attrs]
|
|
11
|
+
sprite_attrs := ('xy=' NUM ',' NUM | 'dir=' NUM | 'size=' NUM
|
|
12
|
+
| 'visible=' BOOL | 'rot=' ROT | 'img=' PATH | 'costume=' NAME)*
|
|
13
|
+
backdrop_attrs:= ('img=' PATH | 'costume=' NAME)*
|
|
14
|
+
|
|
15
|
+
script := hat '{' statement* '}'
|
|
16
|
+
hat := 'when' hat_kind hat_args?
|
|
17
|
+
|
|
18
|
+
statement := block_call
|
|
19
|
+
| var_assign
|
|
20
|
+
| var_modify
|
|
21
|
+
| list_op
|
|
22
|
+
| 'if' expr block ('else' block)?
|
|
23
|
+
| 'repeat' expr block
|
|
24
|
+
| 'repeatUntil' expr block
|
|
25
|
+
| 'forever' block
|
|
26
|
+
| 'while' expr block
|
|
27
|
+
| 'wait' expr
|
|
28
|
+
| 'stop' STOP_ARG
|
|
29
|
+
| 'def' NAME '(' params ')' block # custom block definition
|
|
30
|
+
| custom_call
|
|
31
|
+
|
|
32
|
+
block_call := NAME ('.' NAME)? '(' args? ')' post? # e.g. say(...)(2), pen.Down, goto(1,2)
|
|
33
|
+
| NAME '.' NAME # argument-less form: pen.Down, nextCostume
|
|
34
|
+
| NAME # argument-less: show, hide
|
|
35
|
+
|
|
36
|
+
var_assign := NAME '=' expr
|
|
37
|
+
var_modify := NAME ('+=' | '-=' | '*=' | '/=') expr
|
|
38
|
+
|
|
39
|
+
list_op := NAME '.' ('add' | 'delete' | 'insert' | 'replace') '(' args ')'
|
|
40
|
+
|
|
41
|
+
expr := or_expr
|
|
42
|
+
or_expr := and_expr (('or') and_expr)*
|
|
43
|
+
and_expr := not_expr ('and' not_expr)*
|
|
44
|
+
not_expr := 'not' not_expr | compare
|
|
45
|
+
compare := add (('>' | '<' | '=') add)?
|
|
46
|
+
add := mul (('+' | '-') mul)*
|
|
47
|
+
mul := unary (('*' | '/' | 'mod') unary)*
|
|
48
|
+
unary := '-' unary | atom
|
|
49
|
+
atom := NUM | STRING | NAME | '(' expr ')' | func_call | builtin
|
|
50
|
+
func_call := NAME '(' args? ')'
|
|
51
|
+
|
|
52
|
+
Strings in the .tysh source can be written *without* quotes (because
|
|
53
|
+
"Scratch has no strings, only text"). Where a string vs an identifier
|
|
54
|
+
is ambiguous (e.g. arguments to `say`), the lexer yields a single TEXT
|
|
55
|
+
token whose interpretation is context-sensitive in the parser.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
from __future__ import annotations
|
|
59
|
+
|
|
60
|
+
from dataclasses import dataclass, field
|
|
61
|
+
from typing import List, Optional, Any, Dict
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Top-level
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class Program:
|
|
70
|
+
extensions: List[str] = field(default_factory=list)
|
|
71
|
+
targets: List["Target"] = field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
return f"Program(ext={self.extensions}, targets={len(self.targets)})"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class Target:
|
|
79
|
+
kind: str # 'sprite' or 'stage'
|
|
80
|
+
name: str
|
|
81
|
+
x: float = 0.0
|
|
82
|
+
y: float = 0.0
|
|
83
|
+
direction: float = 90.0
|
|
84
|
+
size: float = 100.0
|
|
85
|
+
visible: bool = True
|
|
86
|
+
rotation_style: str = "all around" # all around | left-right | don't rotate
|
|
87
|
+
img: Optional[str] = None
|
|
88
|
+
costumes: List[Dict[str, Any]] = field(default_factory=list) # {name, path, is_default}
|
|
89
|
+
sounds: List[Dict[str, Any]] = field(default_factory=list)
|
|
90
|
+
scripts: List["Script"] = field(default_factory=list)
|
|
91
|
+
custom_blocks: List["CustomBlockDef"] = field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class Script:
|
|
96
|
+
hat: "Hat"
|
|
97
|
+
body: List["Statement"] = field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
# Hats
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class Hat:
|
|
106
|
+
kind: str # gf | spr | key | backdrop | receive | greater_than | clicked_clone | broadcast
|
|
107
|
+
args: List[Any] = field(default_factory=list)
|
|
108
|
+
line: int = 0
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Statements
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
class Statement:
|
|
116
|
+
line: int = 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class BlockCall(Statement):
|
|
121
|
+
"""A built-in block call: e.g. say(...) , goto(x,y), pen.Down.
|
|
122
|
+
|
|
123
|
+
`namespace` is None for global blocks (say, goto, move, ...).
|
|
124
|
+
`method` is the block name.
|
|
125
|
+
`args` is the list of expressions / atoms inside the parens.
|
|
126
|
+
`post` is the optional trailing paren group, e.g. the seconds in
|
|
127
|
+
`say(Hello, World!)(2)`.
|
|
128
|
+
"""
|
|
129
|
+
namespace: Optional[str]
|
|
130
|
+
method: str
|
|
131
|
+
args: List[Any]
|
|
132
|
+
post: Optional[List[Any]] = None
|
|
133
|
+
line: int = 0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class VarAssign(Statement):
|
|
138
|
+
name: str
|
|
139
|
+
value: Any
|
|
140
|
+
line: int = 0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class VarModify(Statement):
|
|
145
|
+
name: str
|
|
146
|
+
op: str # '+=' | '-=' | '*=' | '/='
|
|
147
|
+
value: Any
|
|
148
|
+
line: int = 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass
|
|
152
|
+
class ListOp(Statement):
|
|
153
|
+
list_name: str
|
|
154
|
+
op: str # 'add' | 'delete' | 'insert' | 'replace' | 'show' | 'hide'
|
|
155
|
+
args: List[Any]
|
|
156
|
+
line: int = 0
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class IfStatement(Statement):
|
|
161
|
+
cond: Any
|
|
162
|
+
then_body: List[Statement]
|
|
163
|
+
else_body: Optional[List[Statement]] = None
|
|
164
|
+
line: int = 0
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass
|
|
168
|
+
class RepeatStatement(Statement):
|
|
169
|
+
times: Any
|
|
170
|
+
body: List[Statement]
|
|
171
|
+
line: int = 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class RepeatUntilStatement(Statement):
|
|
176
|
+
cond: Any
|
|
177
|
+
body: List[Statement]
|
|
178
|
+
line: int = 0
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass
|
|
182
|
+
class WaitUntilStatement(Statement):
|
|
183
|
+
"""`waitUntil (cond)` - control_wait_until."""
|
|
184
|
+
cond: Any
|
|
185
|
+
line: int = 0
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass
|
|
189
|
+
class ForeverStatement(Statement):
|
|
190
|
+
body: List[Statement]
|
|
191
|
+
line: int = 0
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@dataclass
|
|
195
|
+
class WhileStatement(Statement):
|
|
196
|
+
"""Syntactic sugar: `while (c) { ... }` -> `repeatUntil(not c) { ... }`."""
|
|
197
|
+
cond: Any
|
|
198
|
+
body: List[Statement]
|
|
199
|
+
line: int = 0
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass
|
|
203
|
+
class ForEachStatement(Statement):
|
|
204
|
+
"""`forEach (var) in (number) { ... }` - hidden control_for_each block.
|
|
205
|
+
|
|
206
|
+
Iterates `var` from 1 to `count`, running the body once per value.
|
|
207
|
+
`var_name` is the variable to use as the loop counter.
|
|
208
|
+
"""
|
|
209
|
+
var_name: str
|
|
210
|
+
count: Any
|
|
211
|
+
body: List[Statement]
|
|
212
|
+
line: int = 0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass
|
|
216
|
+
class AllAtOnceStatement(Statement):
|
|
217
|
+
"""`allAtOnce { ... }` - hidden control_all_at_once block.
|
|
218
|
+
|
|
219
|
+
Runs the body without yielding to the screen refresh between blocks
|
|
220
|
+
(similar to a warp-mode custom block).
|
|
221
|
+
"""
|
|
222
|
+
body: List[Statement]
|
|
223
|
+
line: int = 0
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@dataclass
|
|
227
|
+
class WaitStatement(Statement):
|
|
228
|
+
secs: Any
|
|
229
|
+
line: int = 0
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@dataclass
|
|
233
|
+
class StopStatement(Statement):
|
|
234
|
+
kind: str # 'all' | 'this script' | 'other scripts in sprite' | 'other scripts in stage'
|
|
235
|
+
line: int = 0
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass
|
|
239
|
+
class BroadcastStatement(Statement):
|
|
240
|
+
msg: Any
|
|
241
|
+
wait: bool = False
|
|
242
|
+
line: int = 0
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@dataclass
|
|
246
|
+
class CustomBlockDef(Statement):
|
|
247
|
+
"""`def MyBlock(name, age) { ... }` - defines a custom block."""
|
|
248
|
+
name: str
|
|
249
|
+
params: List[Dict[str, str]] # [{name, type}] type in {string|bool|number}
|
|
250
|
+
body: List[Statement]
|
|
251
|
+
warp: bool = False # run without screen refresh
|
|
252
|
+
line: int = 0
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@dataclass
|
|
256
|
+
class CustomBlockCall(Statement):
|
|
257
|
+
"""A call to a user-defined custom block."""
|
|
258
|
+
name: str
|
|
259
|
+
args: List[Any]
|
|
260
|
+
line: int = 0
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# Expressions
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
@dataclass
|
|
268
|
+
class NumberLit:
|
|
269
|
+
value: float
|
|
270
|
+
line: int = 0
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass
|
|
274
|
+
class StringLit:
|
|
275
|
+
value: str
|
|
276
|
+
line: int = 0
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@dataclass
|
|
280
|
+
class ColorLit:
|
|
281
|
+
value: str # like "#ff0000"
|
|
282
|
+
line: int = 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@dataclass
|
|
286
|
+
class VarRef:
|
|
287
|
+
name: str
|
|
288
|
+
line: int = 0
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dataclass
|
|
292
|
+
class ListRef:
|
|
293
|
+
name: str
|
|
294
|
+
index: Any
|
|
295
|
+
line: int = 0
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@dataclass
|
|
299
|
+
class BinOp:
|
|
300
|
+
op: str # '+','-','*','/','mod','>','<','=','and','or'
|
|
301
|
+
left: Any
|
|
302
|
+
right: Any
|
|
303
|
+
line: int = 0
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@dataclass
|
|
307
|
+
class UnaryOp:
|
|
308
|
+
op: str # 'not' or '-'
|
|
309
|
+
operand: Any
|
|
310
|
+
line: int = 0
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@dataclass
|
|
314
|
+
class FuncCall:
|
|
315
|
+
"""Operator-like reporter: pickRandom(1,10), join(a,b), abs(x), ..."""
|
|
316
|
+
name: str
|
|
317
|
+
args: List[Any]
|
|
318
|
+
line: int = 0
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@dataclass
|
|
322
|
+
class BuiltinRef:
|
|
323
|
+
"""Direct references to Scratch reporters that have no arguments:
|
|
324
|
+
answer, mouseX, mouseY, timer, loudness, daysSince2000, username,
|
|
325
|
+
x, y, direction, size, costumeName, backdropName, volume, etc.
|
|
326
|
+
"""
|
|
327
|
+
name: str
|
|
328
|
+
line: int = 0
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@dataclass
|
|
332
|
+
class BareRef:
|
|
333
|
+
"""A bare identifier used in expression context.
|
|
334
|
+
|
|
335
|
+
Resolved at codegen time:
|
|
336
|
+
* if the name matches a declared variable in the current target -> VarRef
|
|
337
|
+
* if the name matches a builtin reporter (e.g. "answer", "mouseX") -> BuiltinRef
|
|
338
|
+
* if the name matches a current custom-block parameter -> ArgumentRef
|
|
339
|
+
* otherwise -> StringLit (literal text)
|
|
340
|
+
"""
|
|
341
|
+
name: str
|
|
342
|
+
line: int = 0
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@dataclass
|
|
346
|
+
class ArgumentRef:
|
|
347
|
+
"""Reference to a custom block parameter (an argument_reporter block)."""
|
|
348
|
+
name: str
|
|
349
|
+
arg_id: str = "" # filled in by codegen
|
|
350
|
+
line: int = 0
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@dataclass
|
|
354
|
+
class ListReporterRef:
|
|
355
|
+
"""A list reporter reference like `inventory.item(1)`, `inventory.length`,
|
|
356
|
+
`inventory.contains(thing)`, `inventory.itemNum(thing)`, `inventory.contents`.
|
|
357
|
+
|
|
358
|
+
Resolved at codegen time into the appropriate data_* block.
|
|
359
|
+
"""
|
|
360
|
+
list_name: str
|
|
361
|
+
method: str # 'item' | 'length' | 'contains' | 'itemNum' | 'contents'
|
|
362
|
+
args: List[Any]
|
|
363
|
+
line: int = 0
|