Polyglot

Build Valuable Systems, Better and Faster

Scratch Textual Language

Scratch is a visual programming language, but it might usefully have a textual version as well. Scratch in it’s graphical form can verify syntax intuitively & automatically, and the resulting graphics are definitely beautiful, but it took quite some effort to produce these diagrams when trying to document a program, and it is even harder to reference sections of the diagram.

This activity came out of producing a Scratch Player in Flash, and I wanted to see whether the parse of the file produced the correct program. A textual dump of the parse was an obvious solution, but then this ‘dump’ might as well be the same as a complete program declaration so it was clearly ‘complete’ and I wasn’t missing anything.

Scratch Textual

The core concepts in this proposed textual language for Scratch are:

  • It should match Scratch as closely as possible

  • It should be a readable and easily writeable textual language (vs. XML as an interchange format)

  • It should be translatable to other human languages for the blocks (as Scratch is)

  • It should be really simple to produce and parse

  • Given Scratch is graphical, visual indentation is reasonable

  • It is not a complete ‘dump’ of the Scratch project – no media resources are included.

The above concepts produced the following initial choices:

  • Different sections start with simple keywords: “stage:” and “sprite:” for the main objects, “vars:” and ‘listVars:” for declaring variables, “comment:” for a comment section, and “script:” for declaring a sequence of blocks.

  • Variables simply define their name and an initial value (if desired… otherwise it would be ‘0’)

  • Each block is written out textually with its ‘spec’ – a human readable description of the block and its parameters

  • The parameters to a block (if any) come immediately after, and are preferably indented [say 4 spaces] from the block they are parameters too. Note that the indentation is not necessary because you can tell the number of parameters from the block ‘spec’, but indenting makes it more readable.

  • If a block can contain other blocks, contained blocks must be indented [say 4 spaces] so you can tell they are inside the block vs. after the block. This is in a fashion similar to Python.

  • There should be no blank line within a ‘script:’ section. The end of a script: section is the first blank (non-comment) line.

  • Comments within the textual blocks use ‘#’ to match with Python. Multi-line comments do not exist. Note that Textual Comments are not parsed at all (and do not round-trip), so they should be minimal and merely augment parsing the textual source – use ‘comment:’ for real documentation.

  • ‘script:’ and ‘comment:’ could have position information after them using the ‘loc’ property (e.g. “loc = 500@250”) but this is not likely to be useful since any edits would cause positions to be wrong

An Example

The following is a complete “normal expanded format” version. See further below for a possible optimization to make things more vertically compressed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#===========================
#=== Morph: Stage 
#===========================
stage: Stage

#============
#=== Vars 
#============

vars:
talest-pole-height = 6
previous-moved-disk = 0
potential-disk = 1
num-disks = 5
next-disk = 0
base-disk = 0

listVars:
pole-1 = undefined
pole-2 = undefined
moveable-disks = undefined
pole-3 = undefined

#============
#=== Script 
#============
script:

when 'find-next-disk'
set next-disk to %n
    0
broadcast %e and wait
    propose-next-disk
repeat until %b
    %n > %n
        next-disk
        0
    set potential-disk to %n
    if %b
        not %b
            %n = %n
                potential-disk
                previous-moved-disk
        set next-disk to %n
            potential-disk
broadcast %e and wait
    move-next-disk

#============
#=== Script 
#============
script:

when 'Scratch-StartClicked'
set num-disks to %n
    5
set base-disk to %n
    %n + %n
        num-disks
        1
set next-disk to %n
    num-disks
repeat until %b
    %n < %n
        next-disk
        1
    set next-disk to %n
        %n - %n
            next-disk
            1
broadcast %e and wait
    initialize
set previous-moved-disk to %n
    0
set talest-pole-height to %n
    0
repeat until %b
    %n > %n
        talest-pole-height
        num-disks
    broadcast %e and wait
        find-next-disk
    set talest-pole-height to %n
    if %b
        %n > %n
            talest-pole-height
        set talest-pole-height to %n


#===========================
#=== Morph: Sprite1 
#===========================
sprite: Sprite1

#============
#=== Vars 
#============

vars:
can-move = 3
am-on-top = 1
disk-id = 1
pole = 3
height = 6
previous-pole = 1
pole-at-start = 1

#============
#=== Script 
#============
script:

when 'move-next-disk'
if %b
    %n = %n
        disk-id
        next-disk
    set previous-moved-disk to %n
        disk-id
    set pole-at-start to %n
        pole
    if %b
        %b and %b
            not %b
                %b or %b
                    %n = %n
                        previous-pole
                        1
                    %n = %n
                        pole
                        1
            %n < %n
                disk-id
        set pole to %n
            1
    if %b
        %b and %b
            not %b
                %b or %b
                    %n = %n
                        previous-pole
                        2
                    %n = %n
                        pole
                        2
            %n < %n
                disk-id
        set pole to %n
            2
    if %b
        %b and %b
            not %b
                %b or %b
                    %n = %n
                        previous-pole
                        3
                    %n = %n
                        pole
                        3
            %n < %n
                disk-id
        set pole to %n
            3
    set previous-pole to %n
        pole-at-start
    if %b
        %n = %n
            previous-pole
            1
    if %b
        %n = %n
            previous-pole
            2
    if %b
        %n = %n
            previous-pole
            3
    if %b
        %n = %n
            pole
            1
        set height to %n
    if %b
        %n = %n
            pole
            2
        set height to %n
    if %b
        %n = %n
            pole
            3
        set height to %n

#============
#=== Script 
#============
script:

when 'propose-next-disk'
set am-on-top to %n
    0
set can-move to %n
    0
if %b
    %b and %b
        %n = %n
            pole
            1
        %n = %n
            disk-id
    set am-on-top to %n
        1
if %b
    %b and %b
        %n = %n
            pole
            2
        %n = %n
            disk-id
    set am-on-top to %n
        1
if %b
    %b and %b
        %n = %n
            pole
            3
        %n = %n
            disk-id
    set am-on-top to %n
        1
if %b
    %n > %n
        am-on-top
        0
    if %b
        %b and %b
            not %b
                %b or %b
                    %n = %n
                        previous-pole
                        1
                    %n = %n
                        pole
                        1
            %n < %n
                disk-id
        set can-move to %n
            1
    if %b
        %b and %b
            not %b
                %b or %b
                    %n = %n
                        previous-pole
                        2
                    %n = %n
                        pole
                        2
            %n < %n
                disk-id
        set can-move to %n
            2
    if %b
        %b and %b
            not %b
                %b or %b
                    %n = %n
                        previous-pole
                        3
                    %n = %n
                        pole
                        3
            %n < %n
                disk-id
        set can-move to %n
            3
    if %b
        %n > %n
            can-move
            0

#============
#=== Script 
#============
script:

when 'initialize'
set disk-id to %n
    1
set pole to %n
    1
set previous-pole to %n
    0
set can-move to %n
    0
set am-on-top to %n
    0
set height to %n
    %n + %n
        %n - %n
            base-disk
            disk-id
        1

Vertical Compression

The format above has every block on its own line. This is readable and informative (you can see the argument types) but is vertically expansive. Assuming parentheses or some other paired delimiter are not used for anything else in names, we could replace the argument placeholders with parentheses like below. This gives a more lisp-like feel, at least for the inner evaluations. If you leave one or more placeholders off, they would come from the next lines in ‘missing’ order, kind of like a macro.

1
2
3
4
5
6
7
8
9
10
script:

when 'find-next-disk'
set next-disk to (0)
broadcast (propose-next-disk) and wait
repeat until ((next-disk) > (0))
    set potential-disk to (0)
    if (not ( (potential-disk) = (previous-moved-disk)) )
        set next-disk to (potential-disk)
broadcast (move-next-disk) and wait

Comments