Skip to main content

TypeScratch

A small programming language that compiles to Scratch 3 (.sb3) files. Write text, get a runnable Scratch project you can open in scratch.mit.edu or any Scratch 3 compatible editor.

extension Pen
// your average comment
s "Sprite1"
when gf clicked {
  say(Hello, World!)(2)
  pen.Down
  goto(10, 20)
  pen.Up
  Points = 0
  Name = Scratch Cat
  Points += 1
}
when spr clicked {
  if (days since 2000) > 120 {
    say(idk)
  }
}

Install

pip install typescratch

Or from source:

git clone https://github.com/yourname/typescratch.git
cd typescratch
pip install -e .

CLI

# Compile a .tysh file (writes thing.sb3 next to thing.tysh)
typescratch build thing.tysh

# Specify output path
typescratch build thing.tysh --out custom_name.sb3

# Verbose AST / token / block-graph dump to stderr
typescratch build thing.tysh --debug

Library API

import typescratch

# 1. Compile a .tysh file to a .sb3 file (output defaults to <input>.sb3)
typescratch.file("C:/Users/pc/Desktop/thing.tysh")
typescratch.file("thing.tysh", out="custom.sb3")

# 2. Compile a .tysh file to a specific .sb3 path
typescratch.compile("out.sb3", src_path="thing.tysh")
# or with just the output - it will look for out.tysh
typescratch.compile("out.sb3")

# 3. Compile from a source string
sb3_bytes = typescratch.source('''
    s "Sprite1"
    when gf clicked { say(Hello, World!)(2) }
''')
# or write to a file:
typescratch.source('say(Hello, World!)', out="hello.sb3", filename="hello.tysh")

# 4. Low-level: get the project.json + assets dict
project, assets = typescratch.compile_source(src, debug=True)

Language reference

Comments

// line comment - goes to end of line

Extensions

extension Pen
extension Music
extension Video Sensing
extension Text to Speech

Sprites & Backdrops

// sprite with attributes (all optional - defaults: x=0, y=0, dir=90, size=100)
s "Player" xy=100, 50 dir=90 size=110 visible=true rot=all around

// backdrop
b "Stage 1" img=./backdrops/forest.svg
b "Stage 2"

If img= is omitted or the file doesn't exist, the sprite gets the default Scratch Cat costume and the stage gets a blank white backdrop.

Variables

Score = 0           // creates a variable named Score, initial value 0
Name = Scratch Cat  // creates Name with initial value "Scratch Cat"
Score += 10         // change Score by 10
Score -= 5          // change Score by -5
Score *= 2          // set Score to (Score * 2)
Score /= 3          // set Score to (Score / 3)
showVariable(Score) // show the variable monitor on stage
hideVariable(Score) // hide the variable monitor

Lists

list inventory = [sword, shield, potion]
list scores = [10, 20, 30]

// Stack blocks (modify the list):
inventory.add(new item)
inventory.delete(1)
inventory.delete(all)
inventory.insert(cool thing, 0)
inventory.replace(1, better sword)
inventory.show                       // show the list monitor on stage
inventory.hide                       // hide the list monitor

// Reporters (use in expressions):
X = inventory.item(1)                // item N of list
L = inventory.length                 // length of list
Has = inventory.contains(sword)      // list contains X?  (boolean)
Pos = inventory.itemNum(sword)       // item # of X in list
All = inventory.contents             // list as a string (space-separated)

Hats (events)

when gf clicked { ... }                       // when green flag clicked
when spr clicked { ... }                      // when this sprite clicked
when stage clicked { ... }                    // when stage clicked
when cloned { ... }                           // when I start as a clone
when key [space] pressed { ... }              // when [key] pressed
when backdrop switches to [Stage 2] { ... }   // when backdrop switches to [name]
when I receive [start game] { ... }           // when I receive [broadcast]
when [loudness] > [10] { ... }                // when [property] > [value]

Motion

move(10)                 // move 10 steps
turnRight(15)            // turn right 15 degrees
turnLeft(15)             // turn left 15 degrees
goto(10, 20)             // go to x: 10 y: 20
glide(1, 100, 50)        // glide 1 sec to x:100 y:50
glideTo(2, mouse)        // glide 2 secs to mouse-pointer
pointInDirection(90)     // point in direction 90
pointTowards(mouse)      // point towards mouse-pointer
goTo(random)             // go to random position
changeX(5)               // change x by 5
changeY(5)               // change y by 5
setX(100)                // set x to 100
setY(50)                 // set y to 50
ifOnEdgeBounce           // if on edge, bounce
setRotationStyle(left-right)  // all around | left-right | don't rotate

Reporters: xPosition (alias x), yPosition (alias y), direction

Looks

say(Hello, World!)(2)    // say "Hello, World!" for 2 seconds
say(Just saying)         // say "Just saying" (no time limit)
think(Hmm...)(1)         // think "Hmm..." for 1 second
switchCostumeTo(costume2)
nextCostume
switchBackdropTo(Stage 2)
nextBackdrop
changeSize(10)
setSize(100)
changeEffect(color)(25)
setEffect(color)(0)
clearGraphicEffects
show
hide
goToFront
goToBack(2)

Reporters: costumeName, costumeNumber, backdropName, backdropNumber, size

Sound

playSound(meow)
playSoundUntilDone(meow)
stopAllSounds
changePitch(10)
setPitch(100)
changeVolume(10)
setVolume(100)
changeSoundEffect(pitch)(10)       // pitch | pan
setSoundEffect(pitch)(100)
clearSoundEffects

Reporters: volume, tempo

Control flow

wait(1)                       // wait 1 second
waitUntil (cond)              // wait until condition is true
repeat(10) { ... }            // repeat 10 times
forever { ... }               // repeat forever
if (cond) { ... }
if (cond) { ... } else { ... }
repeatUntil (cond) { ... }
while (cond) { ... }          // sugar: becomes repeatUntil(not cond)
stop(all)                     // all | this script | other scripts in sprite | other scripts in stage
createCloneOf(myself)         // myself | sprite name
deleteThisClone
broadcast(message1)           // broadcast message1
broadcast(message1) and wait  // broadcast and wait

Sensing

ask(What is your name?) and wait   // ask and wait
resetTimer
setDraggable(true)                  // true | false

// Reporters:
answer
mouseDown
mouseX
mouseY
loudness
timer
daysSince2000            // alias: "days since 2000"
username
touching(mouse)          // mouse | edge | sprite name
touchingColor(#ff0000)
colorIsTouching(#ff0000, #00ff00)
distanceTo(mouse)
keyPressed(space)
attribute(x position, Sprite1)     // [property] of [sprite]
current(YEAR)                       // YEAR | MONTH | DATE | DAYOFWEEK | HOUR | MINUTE | SECOND | WEEKOFYEAR

Operators (in expressions)

5 + 3                  // addition
10 - 4                 // subtraction
6 * 7                  // multiplication
20 / 4                 // division
17 mod 5               // modulo
pickRandom(1, 100)     // random integer
join(Hello, World)     // string concatenation
letterOf(1, Hello)     // letter N of string
lengthOf(Hello World)  // string length
contains(Hello World, World)  // string contains
round(3.7)             // round
abs(-5)                // |x|
floor(3.7)  ceiling(3.2)  sqrt(16)
sin(0)  cos(0)  tan(0)  asin(1)  acos(0)  atan(1)
ln(2.7)  log(100)

Comparison: >, <, = (or ==) Logic: and, or, not

Pen extension

pen.Down
pen.Up
pen.SetColor(#ff0000)
pen.ChangeColor(10)
pen.SetSize(2)
pen.ChangeSize(1)
pen.SetHue(180)  pen.ChangeHue(10)
pen.SetShade(50) pen.ChangeShade(10)
pen.SetSaturation(80)  pen.ChangeSaturation(10)
pen.SetBrightness(50)  pen.ChangeBrightness(10)
pen.SetTransparency(0) pen.ChangeTransparency(10)
pen.Stamp
pen.Clear

Custom blocks (My Blocks)

s "Calculator"

// Define a custom block with arguments
def Add(a, b) {
  Result = a + b
  say(join(Result is, Result))
}

// With run-without-screen-refresh (warp)
def FastLoop(n) warp=true {
  repeat(n) { move(1) }
}

// Boolean argument
def IfPositive(n) {
  if n > 0 { say(Yes) } else { say(No) }
}

when gf clicked {
  Add(5, 3)
  FastLoop(100)
  IfPositive(42)
}

Argument types default to string; you can also specify number or bool:

def Compute(base number, bonus number, debug bool) {
  ...
}

Bare names and string literals

TypeScratch follows the principle that Scratch has no strings, only text. Inside a parenthesized argument list, any unquoted word is treated as text by default:

say(Hello, World!)        // MESSAGE = "Hello, World!"
say(LOOK I HAVE A IMAGE!) // MESSAGE = "LOOK I HAVE A IMAGE!"

If a bare word matches a declared variable, a custom-block parameter, or a builtin reporter (like answer, mouseX, timer), it's resolved to that variable/parameter/reporter instead:

Score = 0
Score += 10
say(Score is)(Score)      // "Score is 10"

To force string interpretation, wrap the value in quotes:

say("Score")              // MESSAGE = "Score" (literal text)

Extensions

TypeScratch supports all 11 official Scratch 3.0 extensions. Declare them at the top of your file with extension <Name>:

extension Pen
extension Music
extension Text to Speech
extension Translate
extension Video Sensing
extension Makey Makey
extension micro:bit
extension LEGO BOOST
extension LEGO EV3
extension LEGO WeDo 2
extension Go Direct Force

(Speech to Text is intentionally NOT supported . it's hidden in the Scratch UI and doesn't actually work.)

Many aliases are accepted (e.g. ttstext2speech, wedowedov2, lego mindstormsev3).

Music

music.PlayNote(60, 0.5)             // play note 60 for 0.5 beats
music.PlayDrum(Snare Drum, 0.25)    // play drum sample
music.Rest(0.25)                    // rest for 0.25 beats
music.SetInstrument(Piano)          // set instrument
music.SetTempo(120)
music.ChangeTempo(20)
TempoVar = music.Tempo              // reporter

Text to Speech

tts.Speak(Hello from TypeScratch!)
tts.SetVoice(Alto)                  // alto | tenor | squirrel | kitten | giant | kitten
tts.SetLanguage(English)

Speech to Text

Hidden / experimental . Scratch's Speech to Text extension is hidden in the UI and may not work in all editors. TypeScratch supports it but prints a warning at compile time so you know what you're getting into.

extension Speech to Text

listen.Wait                         // listen and wait
Result = speech                     // get the recognized speech
when i_hear [hello] { ... }         // hat: when I hear "hello"

Translate

SpanishHello = translate.To(Hello, Spanish)
CurrentLang = translate.ViewerLanguage

Video Sensing

video.Toggle(on)                    // on | off
video.SetTransparency(50)
Motion = video.Motion(motion, sprite)   // motion | direction on sprite | stage
when video_motion > 50 { ... }

Makey Makey

when makey_key [Space] pressed { ... }
when makey_code [up up down down] pressed { ... }

micro:bit

mb.DisplayText(Hello)
mb.DisplaySymbol(heart)
mb.DisplayClear
when mb_button [A] pressed { ... }
when mb_gesture [shake] { ... }
when mb_tilted [front] { ... }
when mb_pin [0] connected { ... }
if mb.ButtonPressed(A) { ... }
if mb.Tilted(any) { ... }
Angle = mb.TiltAngle(front)

LEGO BOOST

boost.MotorOnFor(A, 1)              // motor A for 1 second
boost.MotorOnForRotation(B, 2)
boost.MotorOn(A)  boost.MotorOff(A)
boost.SetMotorPower(A, 50)
boost.SetMotorDirection(A, this way)
boost.SetLightHue(180)
Pos = boost.MotorPosition(A)
if boost.SeeingColor(blue) { ... }
Angle = boost.TiltAngle(up)
when boost_color [blue] { ... }
when boost_tilted [any] { ... }

LEGO EV3

ev3.MotorTurnClockwise(A, 1)
ev3.MotorTurnCounterClockwise(B, 1)
ev3.MotorSetPower(C, 75)
ev3.Beep(60, 0.5)
Pos = ev3.MotorPosition(A)
Dist = ev3.Distance
Bright = ev3.Brightness
if ev3.ButtonPressed(1) { ... }
when ev3_button [1] pressed { ... }
when ev3_distance 5 { ... }         // when distance < 5
when ev3_brightness 10 { ... }

LEGO WeDo 2.0

wedo.MotorOnFor(A, 1)
wedo.MotorOn(A)  wedo.MotorOff(A)
wedo.SetMotorPower(A, 50)
wedo.SetMotorDirection(A, this way)
wedo.SetLightHue(180)
wedo.PlayNote(60, 0.5)
Dist = wedo.Distance
if wedo.Tilted(any) { ... }
Angle = wedo.TiltAngle(up)
when wedo_distance [<] [5] { ... }
when wedo_tilted [any] { ... }

Go Direct Force & Acceleration

Force = gdx.Force
if gdx.FreeFalling { ... }
if gdx.Tilted(any) { ... }
Angle = gdx.Tilt(x)
Spin = gdx.SpinSpeed(x)
Acc = gdx.Acceleration(x)
when gdx_gesture [shaken] { ... }
when gdx_force [pushed] { ... }
when gdx_tilted [any] { ... }

Hidden / Hacked / TurboWarp Blocks

TypeScratch supports the hidden blocks that exist in Scratch's runtime but aren't shown in the editor's palette. Use these with care . they may not work in every Scratch editor.

Counter (hidden Control)

resetCounter           // clear the counter
incrementCounter       // increment counter by 1
Count = counter        // reporter: current counter value

forEach loop (hidden Control)

forEach (i) in (5) {
  say(join(Iteration, i))
}
// or: forEach (i in 5) { ... }

Iterates i from 1 to count, running the body once per value.

allAtOnce (hidden Control)

allAtOnce {
  move(10)
  turnRight(15)
  move(10)
}

Runs the body without yielding to the screen refresh between blocks (like a warp-mode custom block).

Stretch (legacy Looks)

changeStretch(10)      // change stretch by 10
setStretch(100)        // set stretch to 100

Other hidden Looks blocks

hideAllSprites                         // hide all sprites in the project
switchBackdropToAndWait(Stage 2)       // switch backdrop to X and wait

Hidden Event hat

when touching [mouse] { ... }          // when touching mouse / edge / sprite

TurboWarp reporters

These return true only when the project is running inside TurboWarp. In vanilla Scratch they return false (and the blocks are invisible).

if isTurboWarp { say(Running in TurboWarp!) }
if isCompiled { say(Compiled mode!) }
if isForked { say(Forked TurboWarp!) }

Errors

Errors include line/column numbers and the source snippet:

CompileError at line 12:19 (thing.tysh): expected ')', got NOT ('not')
  >>   say(No it is not positive)

Use --debug for a full dump of tokens, AST, and the generated block graph.

Examples

See examples/ for runnable programs:

  • hello.tysh - the simplest program
  • pen_spiral.tysh - pen extension demo
  • custom_blocks.tysh - My Blocks with arguments
  • user_example.tysh - the example from the original TypeScratch request
  • full_test.tysh - exercises every supported core feature
  • showcase.tysh - exercises EVERY feature (324 blocks across 4 targets)
  • showcase_extensions.tysh - exercises ALL 12 extensions (Pen, Music, TTS, Speech-to-Text, Translate, Video Sensing, Makey Makey, micro:bit, BOOST, EV3, WeDo 2, Go Direct Force)

Project structure

typescratch/
├── typescratch/
│   ├── __init__.py    # public API: file(), source(), compile(), compile_source()
│   ├── cli.py         # `typescratch build` CLI
│   ├── lexer.py       # .tysh tokenizer
│   ├── parser.py      # tokens -> AST
│   ├── ast_nodes.py   # AST dataclass definitions
│   ├── blocks.py      # TypeScratch syntax -> Scratch opcode table
│   ├── codegen.py     # AST -> Scratch project.json
│   ├── sb3.py         # .sb3 zip packager
│   ├── assets.py      # default Scratch Cat + blank backdrop SVGs
│   └── errors.py      # CompileError + Debug logger
├── examples/          # example .tysh programs
├── pyproject.toml
├── setup.py
└── README.md

License

Apache License 2.0 . see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typescratch-1.3.0.tar.gz (192.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typescratch-1.3.0-py3-none-any.whl (191.4 kB view details)

Uploaded Python 3

File details

Details for the file typescratch-1.3.0.tar.gz.

File metadata

  • Download URL: typescratch-1.3.0.tar.gz
  • Upload date:
  • Size: 192.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.2

File hashes

Hashes for typescratch-1.3.0.tar.gz
Algorithm Hash digest
SHA256 367fe7a561a2d9db920b7beaa758ea19ac4902980f00b3ec695dbdc4389543e3
MD5 1972f6c5d515897be573ee38b8cb76b4
BLAKE2b-256 3ade3b3dce76bdd9b03cfa9a1da1c80c304f219924572e4213f32f3b3c2c7c33

See more details on using hashes here.

File details

Details for the file typescratch-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: typescratch-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 191.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.2

File hashes

Hashes for typescratch-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 40af5f75bf613591691e708a54cec1fb8d4870d0479a801965c9ef5fe655b87c
MD5 514e0aef78215662ef60a25561d90a51
BLAKE2b-256 b32f63b6722d5bffef7f8c0b0e796b3d745fc09cc603b088666a22e031731a89

See more details on using hashes here.

Release history Release notifications | RSS feed

1.7.1

2 files

1.7.0

2 files

1.6.8

2 files

1.6.7

2 files

1.6.2

2 files

1.6.0

2 files

This release

1.3.0 This release

2 files

1.1.0

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page