-
Notifications
You must be signed in to change notification settings - Fork 16
/
measure.js
132 lines (119 loc) · 3.52 KB
/
measure.js
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
/* measure: AST -> height in pixels */
const { sum } = require("itt")
const Scratch = require("./scratch")
var measureLog = function(message) {}
function internalHeight(selector, shape) {
// prettier-ignore
switch (shape) {
case "if-block": return 36
case "c-block cap": return 34 // "forever"
case "cap": return 8
case "c-block": return 21
case "stack": return 9
case "hat": return selector === "whenGreenFlag" ? 25 : 18
case "predicate": return 5 // ###
case "reporter": return 4 // ###
}
throw "internalHeight can't do " + selector
}
function noInputs(shape) {
// prettier-ignore
switch (shape) {
case "stack": return 16
case "cap":
case "c-block cap": return 16
case "predicate": return 16
case "reporter": return 16 // # TODO
case "hat": return emptySlot("readonly-menu")
}
throw "noInputs can't do " + shape
}
function emptySlot(inputShape) {
/* For arguments which are literals, menu options, or just empty */
// prettier-ignore
switch (inputShape) {
case "list": return 12
case "number": return 16
case "string": return 16 // ###
case "boolean": return 16 // ###
case "readonly-menu": return 16 // ###
case "number-menu": return 16 // ###
case "color": return 16 // ###
}
throw "emptySlot can't do " + inputShape
}
function measureList(list, debug) {
if (debug) measureLog = debug
return sum(list.map(measureBlock)) - 3 * (list.length - 1)
}
function measureBlock(block) {
// be careful not to pass a list here (or a block to measureList!)
var selector = block[0],
args = block.slice(1)
if (selector === "procDef") {
var hasInputs = false,
hasBooleans = false
var spec = args[0]
spec.split(Scratch.inputPat).forEach(function(part) {
if (Scratch.inputPat.test(part)) {
hasInputs = true
if (part === "%b") hasBooleans = true
}
})
return hasBooleans ? 65 : hasInputs ? 64 : 60
}
var info = Scratch.blockInfo(block)
if (selector === "call") {
args.shift() // spec
}
var internal = internalHeight(selector, info.shape)
measureLog(internal, "internalHeight", info.selector)
if (selector === "stopScripts" && ["all", "this script"].indexOf(args[0]) === -1) {
internal += 1
}
var argHeight = 0
var stackHeight = 0
var hasInputs = info.inputs.length || /c-block|if-block/.test(info.shape)
if (!hasInputs) {
argHeight = noInputs(info.shape)
measureLog(argHeight, "noInputs", info.shape)
} else {
// has inputs
for (var i = 0; i < args.length; i++) {
var arg = args[i]
var inputShape = info.inputs[i] ? Scratch.getInputShape(info.inputs[i]) : "list"
var nonEmpty = arg instanceof Array && arg.length
// note this could be a *block*!
var foo
if (!nonEmpty) {
foo = emptySlot(inputShape)
measureLog(foo, "emptySlot", inputShape)
}
if (inputShape === "list") {
// c-mouth
if (nonEmpty) {
foo = measureList(arg)
// does it end with a cap block?
var last = arg.slice().pop()
if (last) {
var lastInfo = Scratch.blockInfo(last)
if (/cap/.test(lastInfo.shape)) {
foo += 3
}
}
}
stackHeight += foo
} else {
// arg
if (nonEmpty) {
foo = measureBlock(arg)
}
argHeight = Math.max(argHeight, foo)
}
}
}
var total = internal + argHeight + stackHeight
measureLog(total, block)
return total
}
module.exports = measureList