scratchblocks-plus 1.0.0
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.
- package/LICENSE +19 -0
- package/README.md +193 -0
- package/browser.es.js +8 -0
- package/browser.js +8 -0
- package/build/scratchblocks-plus.min.es.js +12 -0
- package/build/scratchblocks-plus.min.es.js.map +1 -0
- package/build/scratchblocks-plus.min.js +12 -0
- package/build/scratchblocks-plus.min.js.map +1 -0
- package/build/translations-all-es.js +11 -0
- package/build/translations-all-es.js.map +1 -0
- package/build/translations-all.js +11 -0
- package/build/translations-all.js.map +1 -0
- package/build/translations-es.js +11 -0
- package/build/translations-es.js.map +1 -0
- package/build/translations.js +11 -0
- package/build/translations.js.map +1 -0
- package/index.d.ts +297 -0
- package/index.js +229 -0
- package/locales/ab.json +1630 -0
- package/locales/af.json +1630 -0
- package/locales/all.d.ts +108 -0
- package/locales/all.js +161 -0
- package/locales/am.json +1925 -0
- package/locales/an.json +1630 -0
- package/locales/ar.json +1924 -0
- package/locales/ast.json +1630 -0
- package/locales/az.json +1925 -0
- package/locales/be.json +1630 -0
- package/locales/bg.json +1924 -0
- package/locales/bn.json +1630 -0
- package/locales/ca.json +1930 -0
- package/locales/ckb.json +1630 -0
- package/locales/cs.json +1930 -0
- package/locales/cy.json +1929 -0
- package/locales/da.json +1924 -0
- package/locales/de.json +1929 -0
- package/locales/el.json +1931 -0
- package/locales/eo.json +1630 -0
- package/locales/es-419.json +1924 -0
- package/locales/es.json +1929 -0
- package/locales/et.json +1924 -0
- package/locales/eu.json +1924 -0
- package/locales/fa.json +1929 -0
- package/locales/fi.json +1924 -0
- package/locales/fil.json +1631 -0
- package/locales/forums.js +37 -0
- package/locales/fr.json +1929 -0
- package/locales/fy.json +1630 -0
- package/locales/ga.json +1924 -0
- package/locales/gd.json +1929 -0
- package/locales/gl.json +1924 -0
- package/locales/ha.json +1630 -0
- package/locales/he.json +1929 -0
- package/locales/hi.json +1635 -0
- package/locales/hr.json +1929 -0
- package/locales/ht.json +1630 -0
- package/locales/hu.json +1930 -0
- package/locales/hy.json +1630 -0
- package/locales/id.json +1929 -0
- package/locales/is.json +1924 -0
- package/locales/it.json +1929 -0
- package/locales/ja-Hira.json +1637 -0
- package/locales/ja.json +1931 -0
- package/locales/ka.json +1630 -0
- package/locales/kk.json +1632 -0
- package/locales/km.json +1630 -0
- package/locales/ko.json +1924 -0
- package/locales/ku.json +1632 -0
- package/locales/lt.json +1924 -0
- package/locales/lv.json +1924 -0
- package/locales/mi.json +1924 -0
- package/locales/mn.json +1631 -0
- package/locales/nb.json +1929 -0
- package/locales/nl.json +1929 -0
- package/locales/nn.json +1630 -0
- package/locales/nso.json +1630 -0
- package/locales/oc.json +1630 -0
- package/locales/or.json +1631 -0
- package/locales/pl.json +1929 -0
- package/locales/pt-br.json +1924 -0
- package/locales/pt.json +1929 -0
- package/locales/qu.json +1630 -0
- package/locales/rap.json +1632 -0
- package/locales/ro.json +1929 -0
- package/locales/ru.json +1929 -0
- package/locales/sk.json +1924 -0
- package/locales/sl.json +1929 -0
- package/locales/sr.json +1924 -0
- package/locales/sv.json +1924 -0
- package/locales/sw.json +1630 -0
- package/locales/th.json +1924 -0
- package/locales/tn.json +1630 -0
- package/locales/tr.json +1932 -0
- package/locales/uk.json +1924 -0
- package/locales/uz.json +1631 -0
- package/locales/vi.json +1925 -0
- package/locales/xh.json +1630 -0
- package/locales/zh-cn.json +1930 -0
- package/locales/zh-tw.json +1930 -0
- package/locales/zu.json +1918 -0
- package/package.json +81 -0
- package/scratch2/blocks.js +1000 -0
- package/scratch2/draw.js +452 -0
- package/scratch2/filter.js +78 -0
- package/scratch2/index.js +12 -0
- package/scratch2/style.css.js +148 -0
- package/scratch2/style.js +214 -0
- package/scratch3/blocks.js +1134 -0
- package/scratch3/draw.js +334 -0
- package/scratch3/index.js +12 -0
- package/scratch3/style.css.js +280 -0
- package/scratch3/style.js +877 -0
- package/syntax/blocks.js +921 -0
- package/syntax/commands.js +1755 -0
- package/syntax/dropdowns.js +688 -0
- package/syntax/extensions.js +34 -0
- package/syntax/index.js +17 -0
- package/syntax/model.js +566 -0
- package/syntax/syntax.js +1091 -0
package/syntax/syntax.js
ADDED
|
@@ -0,0 +1,1091 @@
|
|
|
1
|
+
function assert(bool, message) {
|
|
2
|
+
if (!bool) {
|
|
3
|
+
throw new Error(`Assertion failed! ${message || ""}`)
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Label,
|
|
9
|
+
Icon,
|
|
10
|
+
Input,
|
|
11
|
+
Block,
|
|
12
|
+
Comment,
|
|
13
|
+
Glow,
|
|
14
|
+
Script,
|
|
15
|
+
Document,
|
|
16
|
+
Matrix,
|
|
17
|
+
} from "./model.js"
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
allLanguages,
|
|
21
|
+
lookupDropdown,
|
|
22
|
+
hexColorPat,
|
|
23
|
+
minifyHash,
|
|
24
|
+
lookupHash,
|
|
25
|
+
hashSpec,
|
|
26
|
+
applyOverrides,
|
|
27
|
+
rtlLanguages,
|
|
28
|
+
iconPat,
|
|
29
|
+
blockName,
|
|
30
|
+
} from "./blocks.js"
|
|
31
|
+
|
|
32
|
+
function paintBlock(info, children, languages) {
|
|
33
|
+
let overrides = []
|
|
34
|
+
if (Array.isArray(children[children.length - 1])) {
|
|
35
|
+
overrides = children.pop()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// build hash
|
|
39
|
+
const words = []
|
|
40
|
+
for (const child of children) {
|
|
41
|
+
if (child.isLabel) {
|
|
42
|
+
words.push(child.value)
|
|
43
|
+
} else if (child.isIcon) {
|
|
44
|
+
words.push(`@${child.name}`)
|
|
45
|
+
} else {
|
|
46
|
+
words.push("_")
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const string = words.join(" ")
|
|
50
|
+
const shortHash = (info.hash = minifyHash(string))
|
|
51
|
+
|
|
52
|
+
// paint
|
|
53
|
+
let lang
|
|
54
|
+
let type
|
|
55
|
+
if (!overrides.includes("reset")) {
|
|
56
|
+
const o = lookupHash(shortHash, info, children, languages, overrides)
|
|
57
|
+
if (o) {
|
|
58
|
+
lang = o.lang
|
|
59
|
+
type = o.type
|
|
60
|
+
info.language = lang
|
|
61
|
+
info.isRTL = rtlLanguages.includes(lang.code)
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
type.shape === "ring"
|
|
65
|
+
? info.shape === "reporter"
|
|
66
|
+
: info.shape === "stack"
|
|
67
|
+
) {
|
|
68
|
+
info.shape = type.shape
|
|
69
|
+
}
|
|
70
|
+
info.category = type.category
|
|
71
|
+
info.categoryIsDefault = true
|
|
72
|
+
// store selector, used for translation among other things
|
|
73
|
+
if (type.selector) {
|
|
74
|
+
info.selector = type.selector
|
|
75
|
+
}
|
|
76
|
+
if (type.id) {
|
|
77
|
+
info.id = type.id
|
|
78
|
+
}
|
|
79
|
+
info.hasLoopArrow = type.hasLoopArrow
|
|
80
|
+
|
|
81
|
+
// ellipsis block
|
|
82
|
+
if (type.spec === ". . .") {
|
|
83
|
+
children = [new Label(". . .")]
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// The block was not recognised, so we check if it's a define block.
|
|
87
|
+
//
|
|
88
|
+
// We check for built-in blocks first to avoid ambiguity, e.g. the
|
|
89
|
+
// `defina o tamanho como (100) %` block in pt_BR.
|
|
90
|
+
for (const lang of languages) {
|
|
91
|
+
if (!isDefineBlock(children, lang)) {
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Setting the shape also triggers some logic in recogniseStuff.
|
|
96
|
+
info.shape = "define-hat"
|
|
97
|
+
info.category = "custom"
|
|
98
|
+
|
|
99
|
+
// Move the children of the define block into an "outline", transforming
|
|
100
|
+
// () and [] shapes as we go.
|
|
101
|
+
const outlineChildren = children
|
|
102
|
+
.splice(
|
|
103
|
+
lang.definePrefix.length,
|
|
104
|
+
children.length - lang.defineSuffix.length,
|
|
105
|
+
)
|
|
106
|
+
.map(child => {
|
|
107
|
+
if (child.isInput && child.isBoolean) {
|
|
108
|
+
// Convert empty boolean slot to empty boolean argument.
|
|
109
|
+
child = paintBlock(
|
|
110
|
+
{
|
|
111
|
+
shape: "boolean",
|
|
112
|
+
argument: "boolean",
|
|
113
|
+
category: "custom-arg",
|
|
114
|
+
},
|
|
115
|
+
[new Label("")],
|
|
116
|
+
languages,
|
|
117
|
+
)
|
|
118
|
+
} else if (
|
|
119
|
+
child.isInput &&
|
|
120
|
+
(child.shape === "string" || child.shape === "number")
|
|
121
|
+
) {
|
|
122
|
+
// Convert string inputs to string arguments, number inputs to number arguments.
|
|
123
|
+
const labels = child.value
|
|
124
|
+
.split(/ +/g)
|
|
125
|
+
.map(word => new Label(word))
|
|
126
|
+
child = paintBlock(
|
|
127
|
+
{
|
|
128
|
+
shape: "reporter",
|
|
129
|
+
argument: child.shape === "string" ? "string" : "number",
|
|
130
|
+
category: "custom-arg",
|
|
131
|
+
},
|
|
132
|
+
labels,
|
|
133
|
+
languages,
|
|
134
|
+
)
|
|
135
|
+
} else if (child.isReporter || child.isBoolean) {
|
|
136
|
+
// Convert variables to number arguments, predicates to boolean arguments.
|
|
137
|
+
if (child.info.categoryIsDefault) {
|
|
138
|
+
child.info.category = "custom-arg"
|
|
139
|
+
}
|
|
140
|
+
child.info.argument = child.isBoolean ? "boolean" : "number"
|
|
141
|
+
}
|
|
142
|
+
return child
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const outlineInfo = {
|
|
146
|
+
shape: "outline",
|
|
147
|
+
category: "custom",
|
|
148
|
+
categoryIsDefault: true,
|
|
149
|
+
hasLoopArrow: false,
|
|
150
|
+
}
|
|
151
|
+
const outline = new Block(outlineInfo, outlineChildren)
|
|
152
|
+
children.splice(lang.definePrefix.length, 0, outline)
|
|
153
|
+
break
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Apply overrides.
|
|
159
|
+
applyOverrides(info, overrides)
|
|
160
|
+
|
|
161
|
+
// dropdowns menus
|
|
162
|
+
children.forEach(child => {
|
|
163
|
+
if (child.hasArrow) {
|
|
164
|
+
child.setMenu(lookupDropdown(child.value, info.id, languages))
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// loop arrows
|
|
169
|
+
if (info.hasLoopArrow) {
|
|
170
|
+
children.push(new Icon("loopArrow"))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const block = new Block(info, children)
|
|
174
|
+
|
|
175
|
+
// image replacement
|
|
176
|
+
if (type && iconPat.test(type.spec)) {
|
|
177
|
+
block.translate(lang, true)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// diffs
|
|
181
|
+
if (info.diff === "+") {
|
|
182
|
+
return new Glow(block)
|
|
183
|
+
}
|
|
184
|
+
block.diff = info.diff
|
|
185
|
+
|
|
186
|
+
return block
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isDefineBlock(children, lang) {
|
|
190
|
+
if (children.length < lang.definePrefix.length) {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
if (children.length < lang.defineSuffix.length) {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (let i = 0; i < lang.definePrefix.length; i++) {
|
|
198
|
+
const defineWord = lang.definePrefix[i]
|
|
199
|
+
const child = children[i]
|
|
200
|
+
if (!child.isLabel || minifyHash(child.value) !== minifyHash(defineWord)) {
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (let i = 1; i <= lang.defineSuffix.length; i++) {
|
|
206
|
+
const defineWord = lang.defineSuffix[lang.defineSuffix.length - i]
|
|
207
|
+
const child = children[children.length - i]
|
|
208
|
+
if (!child.isLabel || minifyHash(child.value) !== minifyHash(defineWord)) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseLines(code, languages) {
|
|
217
|
+
let tok = code[0]
|
|
218
|
+
let index = 0
|
|
219
|
+
function next() {
|
|
220
|
+
tok = code[++index]
|
|
221
|
+
}
|
|
222
|
+
function peek() {
|
|
223
|
+
return code[index + 1]
|
|
224
|
+
}
|
|
225
|
+
function peekNonWs() {
|
|
226
|
+
for (let i = index + 1; i < code.length; i++) {
|
|
227
|
+
if (code[i] !== " ") {
|
|
228
|
+
return code[i]
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
let sawNL
|
|
233
|
+
|
|
234
|
+
let define = []
|
|
235
|
+
languages.map(lang => {
|
|
236
|
+
define = define.concat(lang.define)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
function makeBlock(shape, children) {
|
|
240
|
+
const hasInputs = children.filter(x => !x.isLabel).length
|
|
241
|
+
|
|
242
|
+
const info = {
|
|
243
|
+
shape: shape,
|
|
244
|
+
category: shape === "reporter" && !hasInputs ? "variables" : "obsolete",
|
|
245
|
+
categoryIsDefault: true,
|
|
246
|
+
hasLoopArrow: false,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return paintBlock(info, children, languages)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function makeMenu(shape, value) {
|
|
253
|
+
return new Input(shape, value)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function pParts(end) {
|
|
257
|
+
const children = []
|
|
258
|
+
let label
|
|
259
|
+
while (tok && tok !== "\n") {
|
|
260
|
+
// So that comparison operators `<()<()>` and `<()>()>` don't need the
|
|
261
|
+
// central <> escaped, we interpret it as a label if particular
|
|
262
|
+
// conditions are met.
|
|
263
|
+
if (
|
|
264
|
+
(tok === "<" || tok === ">") &&
|
|
265
|
+
end === ">" && // We're parsing a predicate.
|
|
266
|
+
children.length === 1 && // There's exactly one AST node behind us.
|
|
267
|
+
!children[children.length - 1].isLabel // That node is not a label.
|
|
268
|
+
) {
|
|
269
|
+
const c = peekNonWs()
|
|
270
|
+
// The next token starts some kind of input.
|
|
271
|
+
if (c === "[" || c === "(" || c === "<" || c === "{") {
|
|
272
|
+
label = null
|
|
273
|
+
children.push(new Label(tok))
|
|
274
|
+
next()
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (tok === end) {
|
|
279
|
+
break
|
|
280
|
+
}
|
|
281
|
+
if (tok === "/" && peek() === "/" && !end) {
|
|
282
|
+
break
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
switch (tok) {
|
|
286
|
+
case "[":
|
|
287
|
+
label = null
|
|
288
|
+
children.push(pString())
|
|
289
|
+
break
|
|
290
|
+
case "(":
|
|
291
|
+
label = null
|
|
292
|
+
children.push(pReporter())
|
|
293
|
+
break
|
|
294
|
+
case "<":
|
|
295
|
+
label = null
|
|
296
|
+
children.push(pPredicate())
|
|
297
|
+
break
|
|
298
|
+
case "{":
|
|
299
|
+
label = null
|
|
300
|
+
children.push(pEmbedded())
|
|
301
|
+
break
|
|
302
|
+
case " ":
|
|
303
|
+
case "\t":
|
|
304
|
+
next() // Skip over whitespace.
|
|
305
|
+
label = null
|
|
306
|
+
break
|
|
307
|
+
case "◂":
|
|
308
|
+
case "▸":
|
|
309
|
+
children.push(pIcon())
|
|
310
|
+
label = null
|
|
311
|
+
break
|
|
312
|
+
case "@": {
|
|
313
|
+
next()
|
|
314
|
+
let name = ""
|
|
315
|
+
while (tok && /[a-zA-Z]/.test(tok)) {
|
|
316
|
+
name += tok
|
|
317
|
+
next()
|
|
318
|
+
}
|
|
319
|
+
if (name === "cloud") {
|
|
320
|
+
children.push(new Label("☁"))
|
|
321
|
+
} else {
|
|
322
|
+
children.push(
|
|
323
|
+
Object.prototype.hasOwnProperty.call(Icon.icons, name)
|
|
324
|
+
? new Icon(name)
|
|
325
|
+
: new Label(`@${name}`),
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
label = null
|
|
329
|
+
break
|
|
330
|
+
}
|
|
331
|
+
case "\\":
|
|
332
|
+
next() // escape character
|
|
333
|
+
// fallthrough
|
|
334
|
+
case ":":
|
|
335
|
+
if (tok === ":" && peek() === ":") {
|
|
336
|
+
children.push(pOverrides(end))
|
|
337
|
+
return children
|
|
338
|
+
}
|
|
339
|
+
// fallthrough
|
|
340
|
+
default:
|
|
341
|
+
if (!label) {
|
|
342
|
+
children.push((label = new Label("")))
|
|
343
|
+
}
|
|
344
|
+
label.value += tok
|
|
345
|
+
next()
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return children
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function pString() {
|
|
352
|
+
next() // '['
|
|
353
|
+
let s = ""
|
|
354
|
+
let escapeV = false
|
|
355
|
+
const escaped = tok === "\\"
|
|
356
|
+
while (tok && tok !== "]" && tok !== "\n") {
|
|
357
|
+
if (tok === "\\") {
|
|
358
|
+
next()
|
|
359
|
+
if (tok === "v") {
|
|
360
|
+
escapeV = true
|
|
361
|
+
}
|
|
362
|
+
if (!tok) {
|
|
363
|
+
break
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
escapeV = false
|
|
367
|
+
}
|
|
368
|
+
s += tok
|
|
369
|
+
next()
|
|
370
|
+
}
|
|
371
|
+
if (tok === "]") {
|
|
372
|
+
next()
|
|
373
|
+
}
|
|
374
|
+
if (!escaped && hexColorPat.test(s)) {
|
|
375
|
+
return new Input("color", s)
|
|
376
|
+
}
|
|
377
|
+
return !escapeV && / v$/.test(s)
|
|
378
|
+
? makeMenu("dropdown", s.slice(0, s.length - 2))
|
|
379
|
+
: new Input("string", s)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function pBlock(end) {
|
|
383
|
+
const children = pParts(end)
|
|
384
|
+
if (tok && tok === "\n") {
|
|
385
|
+
sawNL = true
|
|
386
|
+
next()
|
|
387
|
+
}
|
|
388
|
+
if (children.length === 0) {
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// standalone reporters
|
|
393
|
+
if (children.length === 1) {
|
|
394
|
+
const child = children[0]
|
|
395
|
+
if (
|
|
396
|
+
child.isBlock &&
|
|
397
|
+
(child.isReporter || child.isBoolean || child.isRing)
|
|
398
|
+
) {
|
|
399
|
+
return child
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return makeBlock("stack", children)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function pReporter() {
|
|
407
|
+
next() // '('
|
|
408
|
+
|
|
409
|
+
const escaped = tok === "\\"
|
|
410
|
+
|
|
411
|
+
// Check if this is a matrix pattern: ({...} v)
|
|
412
|
+
if (tok === "{") {
|
|
413
|
+
const savedIndex = index
|
|
414
|
+
const savedTok = tok
|
|
415
|
+
|
|
416
|
+
// Try to parse as matrix
|
|
417
|
+
let braceCount = 1
|
|
418
|
+
let matrixContent = ""
|
|
419
|
+
next() // skip '{'
|
|
420
|
+
|
|
421
|
+
while (tok && braceCount > 0) {
|
|
422
|
+
if (tok === "{") {
|
|
423
|
+
braceCount++
|
|
424
|
+
} else if (tok === "}") {
|
|
425
|
+
braceCount--
|
|
426
|
+
if (braceCount === 0) {
|
|
427
|
+
break
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
matrixContent += tok
|
|
431
|
+
next()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check if followed by ' v)'
|
|
435
|
+
if (tok === "}" && braceCount === 0) {
|
|
436
|
+
next() // skip '}'
|
|
437
|
+
|
|
438
|
+
// Check for whitespace and 'v' and ')'
|
|
439
|
+
let afterBrace = 0
|
|
440
|
+
while (afterBrace < code.length && code[index + afterBrace] === " ") {
|
|
441
|
+
afterBrace++
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (
|
|
445
|
+
index + afterBrace < code.length &&
|
|
446
|
+
code[index + afterBrace] === "v" &&
|
|
447
|
+
index + afterBrace + 1 < code.length &&
|
|
448
|
+
code[index + afterBrace + 1] === ")"
|
|
449
|
+
) {
|
|
450
|
+
// This is a matrix! Parse it
|
|
451
|
+
index += afterBrace
|
|
452
|
+
tok = code[index]
|
|
453
|
+
next() // skip 'v'
|
|
454
|
+
next() // skip ')'
|
|
455
|
+
|
|
456
|
+
// Parse the matrix content
|
|
457
|
+
const rows = []
|
|
458
|
+
let currentRow = ""
|
|
459
|
+
|
|
460
|
+
for (let i = 0; i < matrixContent.length; i++) {
|
|
461
|
+
const c = matrixContent[i]
|
|
462
|
+
if (c === "\n" || c === " " || c === "\t") {
|
|
463
|
+
// Skip whitespace
|
|
464
|
+
continue
|
|
465
|
+
} else if (c === ",") {
|
|
466
|
+
// End of row
|
|
467
|
+
if (currentRow.trim()) {
|
|
468
|
+
// Convert string digits to boolean values
|
|
469
|
+
const booleanRow = currentRow
|
|
470
|
+
.trim()
|
|
471
|
+
.split("")
|
|
472
|
+
.map(ch => ch === "1")
|
|
473
|
+
rows.push(booleanRow)
|
|
474
|
+
currentRow = ""
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
// Add to current row
|
|
478
|
+
currentRow += c
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Add last row if any
|
|
483
|
+
if (currentRow.trim()) {
|
|
484
|
+
// Convert string digits to boolean values
|
|
485
|
+
const booleanRow = currentRow
|
|
486
|
+
.trim()
|
|
487
|
+
.split("")
|
|
488
|
+
.map(ch => ch === "1")
|
|
489
|
+
rows.push(booleanRow)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Return as a number-dropdown input containing the matrix
|
|
493
|
+
const matrix = new Matrix(rows)
|
|
494
|
+
const input = new Input("number-dropdown", matrix, null)
|
|
495
|
+
return input
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Reset if not a valid matrix pattern
|
|
500
|
+
index = savedIndex
|
|
501
|
+
tok = savedTok
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// empty number-dropdown
|
|
505
|
+
if (tok === " ") {
|
|
506
|
+
next()
|
|
507
|
+
if (tok === "v" && peek() === ")") {
|
|
508
|
+
next()
|
|
509
|
+
next()
|
|
510
|
+
return new Input("number-dropdown", "")
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const children = pParts(")")
|
|
515
|
+
if (tok && tok === ")") {
|
|
516
|
+
next()
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// empty numbers
|
|
520
|
+
if (children.length === 0) {
|
|
521
|
+
return new Input("number", "")
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// number
|
|
525
|
+
if (children.length === 1 && children[0].isLabel) {
|
|
526
|
+
const value = children[0].value
|
|
527
|
+
if (/^[0-9e.-]*$/.test(value)) {
|
|
528
|
+
return new Input("number", value)
|
|
529
|
+
}
|
|
530
|
+
if (!escaped && hexColorPat.test(value)) {
|
|
531
|
+
return new Input("color", value)
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// number-dropdown
|
|
536
|
+
if (children.length > 1 && children.every(child => child.isLabel)) {
|
|
537
|
+
const last = children[children.length - 1]
|
|
538
|
+
if (last.value === "v") {
|
|
539
|
+
children.pop()
|
|
540
|
+
const value = children.map(l => l.value).join(" ")
|
|
541
|
+
return makeMenu("number-dropdown", value)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const block = makeBlock("reporter", children)
|
|
546
|
+
|
|
547
|
+
// rings
|
|
548
|
+
if (block.info && block.info.shape === "ring") {
|
|
549
|
+
const first = block.children[0]
|
|
550
|
+
if (
|
|
551
|
+
first &&
|
|
552
|
+
first.isInput &&
|
|
553
|
+
first.shape === "number" &&
|
|
554
|
+
first.value === ""
|
|
555
|
+
) {
|
|
556
|
+
block.children[0] = new Input("reporter")
|
|
557
|
+
} else if (
|
|
558
|
+
(first && first.isScript && first.isEmpty) ||
|
|
559
|
+
(first && first.isBlock && !first.children.length)
|
|
560
|
+
) {
|
|
561
|
+
block.children[0] = new Input("stack")
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return block
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function pPredicate() {
|
|
569
|
+
next() // '<'
|
|
570
|
+
const children = pParts(">")
|
|
571
|
+
if (tok && tok === ">") {
|
|
572
|
+
next()
|
|
573
|
+
}
|
|
574
|
+
if (children.length === 0) {
|
|
575
|
+
return new Input("boolean")
|
|
576
|
+
}
|
|
577
|
+
return makeBlock("boolean", children)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function pEmbedded() {
|
|
581
|
+
next() // '{'
|
|
582
|
+
|
|
583
|
+
sawNL = false
|
|
584
|
+
const f = function () {
|
|
585
|
+
while (tok && tok !== "}") {
|
|
586
|
+
const block = pBlock("}")
|
|
587
|
+
if (block) {
|
|
588
|
+
return block
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const scripts = parseScripts(f)
|
|
593
|
+
let blocks = []
|
|
594
|
+
scripts.forEach(script => {
|
|
595
|
+
blocks = blocks.concat(script.blocks)
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
if (tok === "}") {
|
|
599
|
+
next()
|
|
600
|
+
}
|
|
601
|
+
if (!sawNL) {
|
|
602
|
+
assert(blocks.length <= 1)
|
|
603
|
+
return blocks.length ? blocks[0] : makeBlock("stack", [])
|
|
604
|
+
}
|
|
605
|
+
return new Script(blocks)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function pIcon() {
|
|
609
|
+
const c = tok
|
|
610
|
+
next()
|
|
611
|
+
switch (c) {
|
|
612
|
+
case "▸":
|
|
613
|
+
return new Icon("addInput")
|
|
614
|
+
case "◂":
|
|
615
|
+
return new Icon("delInput")
|
|
616
|
+
default:
|
|
617
|
+
return
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function pOverrides(end) {
|
|
622
|
+
next()
|
|
623
|
+
next()
|
|
624
|
+
const overrides = []
|
|
625
|
+
let override = ""
|
|
626
|
+
while (tok && tok !== "\n" && tok !== end) {
|
|
627
|
+
if (tok === " ") {
|
|
628
|
+
if (override) {
|
|
629
|
+
overrides.push(override)
|
|
630
|
+
override = ""
|
|
631
|
+
}
|
|
632
|
+
} else if (tok === "/" && peek() === "/") {
|
|
633
|
+
break
|
|
634
|
+
} else {
|
|
635
|
+
override += tok
|
|
636
|
+
}
|
|
637
|
+
next()
|
|
638
|
+
}
|
|
639
|
+
if (override) {
|
|
640
|
+
overrides.push(override)
|
|
641
|
+
}
|
|
642
|
+
return overrides
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function pComment(end) {
|
|
646
|
+
next()
|
|
647
|
+
next()
|
|
648
|
+
let comment = ""
|
|
649
|
+
while (tok && tok !== "\n" && tok !== end) {
|
|
650
|
+
comment += tok
|
|
651
|
+
next()
|
|
652
|
+
}
|
|
653
|
+
if (tok && tok === "\n") {
|
|
654
|
+
next()
|
|
655
|
+
}
|
|
656
|
+
return new Comment(comment, true)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function pLine() {
|
|
660
|
+
let diff
|
|
661
|
+
if (tok === "+" || tok === "-") {
|
|
662
|
+
diff = tok
|
|
663
|
+
next()
|
|
664
|
+
}
|
|
665
|
+
const block = pBlock()
|
|
666
|
+
if (tok === "/" && peek() === "/") {
|
|
667
|
+
const comment = pComment()
|
|
668
|
+
comment.hasBlock = block && block.children.length
|
|
669
|
+
if (!comment.hasBlock) {
|
|
670
|
+
return comment
|
|
671
|
+
}
|
|
672
|
+
block.comment = comment
|
|
673
|
+
}
|
|
674
|
+
if (block) {
|
|
675
|
+
block.diff = diff
|
|
676
|
+
}
|
|
677
|
+
return block
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return () => {
|
|
681
|
+
if (!tok) {
|
|
682
|
+
return undefined
|
|
683
|
+
}
|
|
684
|
+
const line = pLine()
|
|
685
|
+
return line || "NL"
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/* * */
|
|
690
|
+
|
|
691
|
+
function parseScripts(getLine) {
|
|
692
|
+
let line = getLine()
|
|
693
|
+
function next() {
|
|
694
|
+
line = getLine()
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function pFile() {
|
|
698
|
+
while (line === "NL") {
|
|
699
|
+
next()
|
|
700
|
+
}
|
|
701
|
+
const scripts = []
|
|
702
|
+
while (line) {
|
|
703
|
+
let blocks = []
|
|
704
|
+
while (line && line !== "NL") {
|
|
705
|
+
let b = pLine()
|
|
706
|
+
const isGlow = b.diff === "+"
|
|
707
|
+
if (isGlow) {
|
|
708
|
+
b.diff = null
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (b.isElse || b.isEnd) {
|
|
712
|
+
b = new Block({ ...b.info, shape: "stack" }, b.children)
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (isGlow) {
|
|
716
|
+
const last = blocks[blocks.length - 1]
|
|
717
|
+
let children = []
|
|
718
|
+
if (last && last.isGlow) {
|
|
719
|
+
blocks.pop()
|
|
720
|
+
children = last.child.isScript ? last.child.blocks : [last.child]
|
|
721
|
+
}
|
|
722
|
+
children.push(b)
|
|
723
|
+
blocks.push(new Glow(new Script(children)))
|
|
724
|
+
} else if (b.isHat) {
|
|
725
|
+
if (blocks.length) {
|
|
726
|
+
scripts.push(new Script(blocks))
|
|
727
|
+
}
|
|
728
|
+
blocks = [b]
|
|
729
|
+
} else if (b.isFinal) {
|
|
730
|
+
blocks.push(b)
|
|
731
|
+
break
|
|
732
|
+
} else if (b.isCommand) {
|
|
733
|
+
blocks.push(b)
|
|
734
|
+
} else {
|
|
735
|
+
// reporter or predicate
|
|
736
|
+
if (blocks.length) {
|
|
737
|
+
scripts.push(new Script(blocks))
|
|
738
|
+
}
|
|
739
|
+
scripts.push(new Script([b]))
|
|
740
|
+
blocks = []
|
|
741
|
+
break
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (blocks.length) {
|
|
745
|
+
scripts.push(new Script(blocks))
|
|
746
|
+
}
|
|
747
|
+
while (line === "NL") {
|
|
748
|
+
next()
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return scripts
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function pLine() {
|
|
755
|
+
const b = line
|
|
756
|
+
next()
|
|
757
|
+
|
|
758
|
+
if (b.hasScript) {
|
|
759
|
+
while (true) {
|
|
760
|
+
const blocks = pMouth()
|
|
761
|
+
b.children.push(new Script(blocks))
|
|
762
|
+
if (line && line.isElse) {
|
|
763
|
+
for (const child of line.children) {
|
|
764
|
+
b.children.push(child)
|
|
765
|
+
}
|
|
766
|
+
next()
|
|
767
|
+
continue
|
|
768
|
+
}
|
|
769
|
+
if (line && line.isEnd) {
|
|
770
|
+
next()
|
|
771
|
+
}
|
|
772
|
+
break
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return b
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function pMouth() {
|
|
779
|
+
const blocks = []
|
|
780
|
+
while (line) {
|
|
781
|
+
if (line === "NL") {
|
|
782
|
+
next()
|
|
783
|
+
continue
|
|
784
|
+
}
|
|
785
|
+
if (!line.isCommand) {
|
|
786
|
+
return blocks
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const b = pLine()
|
|
790
|
+
const isGlow = b.diff === "+"
|
|
791
|
+
if (isGlow) {
|
|
792
|
+
b.diff = null
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (isGlow) {
|
|
796
|
+
const last = blocks[blocks.length - 1]
|
|
797
|
+
let children = []
|
|
798
|
+
if (last && last.isGlow) {
|
|
799
|
+
blocks.pop()
|
|
800
|
+
children = last.child.isScript ? last.child.blocks : [last.child]
|
|
801
|
+
}
|
|
802
|
+
children.push(b)
|
|
803
|
+
blocks.push(new Glow(new Script(children)))
|
|
804
|
+
} else {
|
|
805
|
+
blocks.push(b)
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return blocks
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return pFile()
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/* * */
|
|
815
|
+
|
|
816
|
+
function eachBlock(x, cb) {
|
|
817
|
+
if (x.isScript) {
|
|
818
|
+
x.blocks = x.blocks.map(block => {
|
|
819
|
+
eachBlock(block, cb)
|
|
820
|
+
return cb(block) || block
|
|
821
|
+
})
|
|
822
|
+
} else if (x.isBlock) {
|
|
823
|
+
x.children = x.children.map(child => {
|
|
824
|
+
eachBlock(child, cb)
|
|
825
|
+
return cb(child) || child
|
|
826
|
+
})
|
|
827
|
+
} else if (x.isGlow) {
|
|
828
|
+
eachBlock(x.child, cb)
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const listBlocks = {
|
|
833
|
+
"append:toList:": 1,
|
|
834
|
+
"deleteLine:ofList:": 1,
|
|
835
|
+
"insert:at:ofList:": 2,
|
|
836
|
+
"setLine:ofList:to:": 1,
|
|
837
|
+
"showList:": 0,
|
|
838
|
+
"hideList:": 0,
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function recogniseStuff(scripts) {
|
|
842
|
+
const customBlocksByHash = Object.create(null)
|
|
843
|
+
const listNames = new Set()
|
|
844
|
+
|
|
845
|
+
scripts.forEach(script => {
|
|
846
|
+
const customArgs = new Set()
|
|
847
|
+
|
|
848
|
+
eachBlock(script, block => {
|
|
849
|
+
if (!block.isBlock) {
|
|
850
|
+
return
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// custom blocks
|
|
854
|
+
if (block.info.shape === "define-hat") {
|
|
855
|
+
// There should be exactly one `outline` child, added in paintBlock.
|
|
856
|
+
const outline = block.children.find(child => child.isOutline)
|
|
857
|
+
if (!outline) {
|
|
858
|
+
return
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const names = []
|
|
862
|
+
const parts = []
|
|
863
|
+
for (const child of outline.children) {
|
|
864
|
+
if (child.isLabel) {
|
|
865
|
+
parts.push(child.value)
|
|
866
|
+
} else if (child.isBlock) {
|
|
867
|
+
if (!child.info.argument) {
|
|
868
|
+
return
|
|
869
|
+
}
|
|
870
|
+
parts.push(
|
|
871
|
+
{
|
|
872
|
+
number: "%n",
|
|
873
|
+
string: "%s",
|
|
874
|
+
boolean: "%b",
|
|
875
|
+
}[child.info.argument],
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
const name = blockName(child)
|
|
879
|
+
names.push(name)
|
|
880
|
+
customArgs.add(name)
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
const spec = parts.join(" ")
|
|
884
|
+
const hash = hashSpec(spec)
|
|
885
|
+
|
|
886
|
+
const info = {
|
|
887
|
+
spec: spec,
|
|
888
|
+
names: names,
|
|
889
|
+
}
|
|
890
|
+
if (!customBlocksByHash[hash]) {
|
|
891
|
+
customBlocksByHash[hash] = info
|
|
892
|
+
}
|
|
893
|
+
block.info.id = "PROCEDURES_DEFINITION"
|
|
894
|
+
block.info.selector = "procDef"
|
|
895
|
+
block.info.call = info.spec
|
|
896
|
+
block.info.names = info.names
|
|
897
|
+
block.info.category = "custom"
|
|
898
|
+
|
|
899
|
+
// custom arguments
|
|
900
|
+
} else if (
|
|
901
|
+
block.info.categoryIsDefault &&
|
|
902
|
+
(block.isReporter || block.isBoolean)
|
|
903
|
+
) {
|
|
904
|
+
const name = blockName(block)
|
|
905
|
+
if (customArgs.has(name)) {
|
|
906
|
+
block.info.category = "custom-arg"
|
|
907
|
+
block.info.categoryIsDefault = false
|
|
908
|
+
block.info.selector = "getParam"
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// list names
|
|
912
|
+
} else if (
|
|
913
|
+
Object.prototype.hasOwnProperty.call(listBlocks, block.info.selector)
|
|
914
|
+
) {
|
|
915
|
+
const argIndex = listBlocks[block.info.selector]
|
|
916
|
+
const inputs = block.children.filter(child => !child.isLabel)
|
|
917
|
+
const input = inputs[argIndex]
|
|
918
|
+
if (input && input.isInput) {
|
|
919
|
+
listNames.add(input.value)
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
})
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
scripts.forEach(script => {
|
|
926
|
+
eachBlock(script, block => {
|
|
927
|
+
if (
|
|
928
|
+
block.info &&
|
|
929
|
+
// Recognise custom calls even if user applied :: custom override.
|
|
930
|
+
// Accept either default 'obsolete' category or explicit 'custom' with no selector set.
|
|
931
|
+
((block.info.categoryIsDefault && block.info.category === "obsolete") ||
|
|
932
|
+
(block.info.category === "custom" && !block.info.selector))
|
|
933
|
+
) {
|
|
934
|
+
// custom blocks
|
|
935
|
+
const info = customBlocksByHash[block.info.hash]
|
|
936
|
+
if (info) {
|
|
937
|
+
block.info.id = "PROCEDURES_CALL"
|
|
938
|
+
block.info.selector = "call"
|
|
939
|
+
block.info.call = info.spec
|
|
940
|
+
block.info.names = info.names
|
|
941
|
+
block.info.category = "custom"
|
|
942
|
+
}
|
|
943
|
+
return
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
let name, info
|
|
947
|
+
if (
|
|
948
|
+
block.isReporter &&
|
|
949
|
+
block.info.category === "variables" &&
|
|
950
|
+
block.info.categoryIsDefault
|
|
951
|
+
) {
|
|
952
|
+
// We set the selector here for some reason
|
|
953
|
+
block.info.selector = "readVariable"
|
|
954
|
+
name = blockName(block)
|
|
955
|
+
info = block.info
|
|
956
|
+
}
|
|
957
|
+
if (!name) {
|
|
958
|
+
return
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// list reporters
|
|
962
|
+
if (listNames.has(name)) {
|
|
963
|
+
info.category = "list"
|
|
964
|
+
info.categoryIsDefault = false
|
|
965
|
+
info.selector = "contentsOfList:"
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
return // already done
|
|
969
|
+
})
|
|
970
|
+
})
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Assign block paths and build the block map for a document.
|
|
975
|
+
* Path format: {scriptIndex}.{blockIndex}[.{childIndex}]*
|
|
976
|
+
* Example: 1.2.1 = Script 1, Block 2, Child Block 1
|
|
977
|
+
*/
|
|
978
|
+
function assignBlockPaths(doc) {
|
|
979
|
+
function processBlock(block, basePath, blockMap) {
|
|
980
|
+
if (!block || !block.isBlock) return
|
|
981
|
+
|
|
982
|
+
block.blockPath = basePath
|
|
983
|
+
blockMap.set(basePath, block)
|
|
984
|
+
|
|
985
|
+
// Process nested blocks in children
|
|
986
|
+
let childBlockIndex = 0
|
|
987
|
+
for (const child of block.children) {
|
|
988
|
+
if (child.isBlock) {
|
|
989
|
+
childBlockIndex++
|
|
990
|
+
processBlock(child, `${basePath}.${childBlockIndex}`, blockMap)
|
|
991
|
+
} else if (child.isScript) {
|
|
992
|
+
// Handle C-block mouths (if/else/repeat bodies)
|
|
993
|
+
childBlockIndex++
|
|
994
|
+
let innerBlockIndex = 0
|
|
995
|
+
for (const innerBlock of child.blocks) {
|
|
996
|
+
innerBlockIndex++
|
|
997
|
+
if (innerBlock.isBlock) {
|
|
998
|
+
processBlock(
|
|
999
|
+
innerBlock,
|
|
1000
|
+
`${basePath}.${childBlockIndex}.${innerBlockIndex}`,
|
|
1001
|
+
blockMap,
|
|
1002
|
+
)
|
|
1003
|
+
} else if (innerBlock.isGlow) {
|
|
1004
|
+
processGlow(
|
|
1005
|
+
innerBlock,
|
|
1006
|
+
`${basePath}.${childBlockIndex}.${innerBlockIndex}`,
|
|
1007
|
+
blockMap,
|
|
1008
|
+
)
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
} else if (child.isGlow) {
|
|
1012
|
+
childBlockIndex++
|
|
1013
|
+
processGlow(child, `${basePath}.${childBlockIndex}`, blockMap)
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function processGlow(glow, basePath, blockMap) {
|
|
1019
|
+
if (!glow || !glow.isGlow) return
|
|
1020
|
+
|
|
1021
|
+
const child = glow.child
|
|
1022
|
+
if (child.isBlock) {
|
|
1023
|
+
processBlock(child, basePath, blockMap)
|
|
1024
|
+
} else if (child.isScript) {
|
|
1025
|
+
let innerBlockIndex = 0
|
|
1026
|
+
for (const innerBlock of child.blocks) {
|
|
1027
|
+
innerBlockIndex++
|
|
1028
|
+
if (innerBlock.isBlock) {
|
|
1029
|
+
processBlock(innerBlock, `${basePath}.${innerBlockIndex}`, blockMap)
|
|
1030
|
+
} else if (innerBlock.isGlow) {
|
|
1031
|
+
processGlow(innerBlock, `${basePath}.${innerBlockIndex}`, blockMap)
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
doc.scripts.forEach((script, scriptIdx) => {
|
|
1038
|
+
script.scriptIndex = scriptIdx + 1
|
|
1039
|
+
let blockIndex = 0
|
|
1040
|
+
|
|
1041
|
+
for (const block of script.blocks) {
|
|
1042
|
+
blockIndex++
|
|
1043
|
+
const basePath = `${scriptIdx + 1}.${blockIndex}`
|
|
1044
|
+
|
|
1045
|
+
if (block.isBlock) {
|
|
1046
|
+
processBlock(block, basePath, doc.blockMap)
|
|
1047
|
+
} else if (block.isGlow) {
|
|
1048
|
+
processGlow(block, basePath, doc.blockMap)
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
})
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export function parse(code, options) {
|
|
1055
|
+
options = {
|
|
1056
|
+
inline: false,
|
|
1057
|
+
languages: ["en"],
|
|
1058
|
+
...options,
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if (options.dialect) {
|
|
1062
|
+
throw new Error("Option 'dialect' no longer supported")
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
code = code.replace(/</g, "<")
|
|
1066
|
+
code = code.replace(/>/g, ">")
|
|
1067
|
+
if (options.inline) {
|
|
1068
|
+
code = code.replace(/\n/g, " ")
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const languages = options.languages.map(code => {
|
|
1072
|
+
const lang = allLanguages[code]
|
|
1073
|
+
if (!lang) {
|
|
1074
|
+
throw new Error(`Unknown language: '${code}'`)
|
|
1075
|
+
}
|
|
1076
|
+
return lang
|
|
1077
|
+
})
|
|
1078
|
+
|
|
1079
|
+
/* * */
|
|
1080
|
+
|
|
1081
|
+
const f = parseLines(code, languages)
|
|
1082
|
+
const scripts = parseScripts(f)
|
|
1083
|
+
recogniseStuff(scripts)
|
|
1084
|
+
|
|
1085
|
+
const doc = new Document(scripts)
|
|
1086
|
+
|
|
1087
|
+
// Assign block paths for highlighting support
|
|
1088
|
+
assignBlockPaths(doc)
|
|
1089
|
+
|
|
1090
|
+
return doc
|
|
1091
|
+
}
|