-
Notifications
You must be signed in to change notification settings - Fork 360
/
Copy pathprotofier.dart
368 lines (330 loc) · 14.5 KB
/
protofier.dart
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
// Copyright 2019 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import '../util/map.dart';
import '../util/nullable.dart';
import '../value.dart';
import 'dispatcher.dart';
import 'embedded_sass.pb.dart' as proto;
import 'embedded_sass.pb.dart' hide Value, ListSeparator, CalculationOperator;
import 'function_registry.dart';
import 'host_callable.dart';
import 'utils.dart';
/// A class that converts Sass [Value] objects into [Value] protobufs.
///
/// A given [Protofier] instance is valid only within the scope of a single
/// custom function call.
final class Protofier {
/// The dispatcher, for invoking deprotofied [Value_HostFunction]s.
final Dispatcher _dispatcher;
/// The IDs of first-class functions.
final FunctionRegistry _functions;
/// Any argument lists transitively contained in [value].
///
/// The IDs of the [Value_ArgumentList] protobufs are always one greater than
/// the index of the corresponding list in this array (since 0 is reserved for
/// argument lists created by the host).
final _argumentLists = <SassArgumentList>[];
/// Creates a [Protofier] that's valid within the scope of a single custom
/// function call.
///
/// The [functions] tracks the IDs of first-class functions so that the host
/// can pass them back to the compiler.
Protofier(this._dispatcher, this._functions);
/// Converts [value] to its protocol buffer representation.
proto.Value protofy(Value value) {
var result = proto.Value();
switch (value) {
case SassString():
result.string = Value_String()
..text = value.text
..quoted = value.hasQuotes;
case SassNumber():
result.number = _protofyNumber(value);
case SassColor(hasCalculatedHsl: true):
result.hslColor = Value_HslColor()
..hue = value.hue * 1.0
..saturation = value.saturation * 1.0
..lightness = value.lightness * 1.0
..alpha = value.alpha * 1.0;
case SassColor():
result.rgbColor = Value_RgbColor()
..red = value.red
..green = value.green
..blue = value.blue
..alpha = value.alpha * 1.0;
case SassArgumentList():
_argumentLists.add(value);
result.argumentList = Value_ArgumentList()
..id = _argumentLists.length
..separator = _protofySeparator(value.separator)
..keywords.addAll({
for (var (key, value) in value.keywordsWithoutMarking.pairs)
key: protofy(value)
})
..contents.addAll(value.asList.map(protofy));
case SassList():
result.list = Value_List()
..separator = _protofySeparator(value.separator)
..hasBrackets = value.hasBrackets
..contents.addAll(value.asList.map(protofy));
case SassMap():
result.map = Value_Map();
for (var (key, value) in value.contents.pairs) {
result.map.entries.add(Value_Map_Entry()
..key = protofy(key)
..value = protofy(value));
}
case SassCalculation():
result.calculation = _protofyCalculation(value);
case SassFunction():
result.compilerFunction = _functions.protofy(value);
case sassTrue:
result.singleton = SingletonValue.TRUE;
case sassFalse:
result.singleton = SingletonValue.FALSE;
case sassNull:
result.singleton = SingletonValue.NULL;
case _:
throw "Unknown Value $value";
}
return result;
}
/// Converts [number] to its protocol buffer representation.
Value_Number _protofyNumber(SassNumber number) => Value_Number()
..value = number.value * 1.0
..numerators.addAll(number.numeratorUnits)
..denominators.addAll(number.denominatorUnits);
/// Converts [separator] to its protocol buffer representation.
proto.ListSeparator _protofySeparator(ListSeparator separator) =>
switch (separator) {
ListSeparator.comma => proto.ListSeparator.COMMA,
ListSeparator.space => proto.ListSeparator.SPACE,
ListSeparator.slash => proto.ListSeparator.SLASH,
ListSeparator.undecided => proto.ListSeparator.UNDECIDED
};
/// Converts [calculation] to its protocol buffer representation.
Value_Calculation _protofyCalculation(SassCalculation calculation) =>
Value_Calculation()
..name = calculation.name
..arguments.addAll(calculation.arguments.map(_protofyCalculationValue));
/// Converts a calculation value that appears within a `SassCalculation` to
/// its protocol buffer representation.
Value_Calculation_CalculationValue _protofyCalculationValue(Object value) {
var result = Value_Calculation_CalculationValue();
switch (value) {
case SassNumber():
result.number = _protofyNumber(value);
case SassCalculation():
result.calculation = _protofyCalculation(value);
case SassString():
result.string = value.text;
case CalculationOperation():
result.operation = Value_Calculation_CalculationOperation()
..operator = _protofyCalculationOperator(value.operator)
..left = _protofyCalculationValue(value.left)
..right = _protofyCalculationValue(value.right);
case _:
throw "Unknown calculation value $value";
}
return result;
}
/// Converts [operator] to its protocol buffer representation.
proto.CalculationOperator _protofyCalculationOperator(
CalculationOperator operator) =>
switch (operator) {
CalculationOperator.plus => proto.CalculationOperator.PLUS,
CalculationOperator.minus => proto.CalculationOperator.MINUS,
CalculationOperator.times => proto.CalculationOperator.TIMES,
CalculationOperator.dividedBy => proto.CalculationOperator.DIVIDE
};
/// Converts [response]'s return value to its Sass representation.
Value deprotofyResponse(InboundMessage_FunctionCallResponse response) {
for (var id in response.accessedArgumentLists) {
// Mark the `keywords` field as accessed.
_argumentListForId(id).keywords;
}
return _deprotofy(response.success);
}
/// Converts [value] to its Sass representation.
Value _deprotofy(proto.Value value) {
try {
switch (value.whichValue()) {
case Value_Value.string:
return value.string.text.isEmpty
? SassString.empty(quotes: value.string.quoted)
: SassString(value.string.text, quotes: value.string.quoted);
case Value_Value.number:
return _deprotofyNumber(value.number);
case Value_Value.rgbColor:
return SassColor.rgb(value.rgbColor.red, value.rgbColor.green,
value.rgbColor.blue, value.rgbColor.alpha);
case Value_Value.hslColor:
return SassColor.hsl(value.hslColor.hue, value.hslColor.saturation,
value.hslColor.lightness, value.hslColor.alpha);
case Value_Value.hwbColor:
return SassColor.hwb(value.hwbColor.hue, value.hwbColor.whiteness,
value.hwbColor.blackness, value.hwbColor.alpha);
case Value_Value.argumentList:
if (value.argumentList.id != 0) {
return _argumentListForId(value.argumentList.id);
}
var separator = _deprotofySeparator(value.argumentList.separator);
var length = value.argumentList.contents.length;
if (separator == ListSeparator.undecided && length > 1) {
throw paramsError(
"List $value can't have an undecided separator because it has "
"$length elements");
}
return SassArgumentList(
value.argumentList.contents.map(_deprotofy),
{
for (var (name, value) in value.argumentList.keywords.pairs)
name: _deprotofy(value)
},
separator);
case Value_Value.list:
var separator = _deprotofySeparator(value.list.separator);
if (value.list.contents.isEmpty) {
return SassList.empty(
separator: separator, brackets: value.list.hasBrackets);
}
var length = value.list.contents.length;
if (separator == ListSeparator.undecided && length > 1) {
throw paramsError(
"List $value can't have an undecided separator because it has "
"$length elements");
}
return SassList(value.list.contents.map(_deprotofy), separator,
brackets: value.list.hasBrackets);
case Value_Value.map:
return value.map.entries.isEmpty
? const SassMap.empty()
: SassMap({
for (var Value_Map_Entry(:key, :value) in value.map.entries)
_deprotofy(key): _deprotofy(value)
});
case Value_Value.compilerFunction:
var id = value.compilerFunction.id;
if (_functions[id] case var function?) return function;
throw paramsError(
"CompilerFunction.id $id doesn't match any known functions");
case Value_Value.hostFunction:
return SassFunction(hostCallable(
_dispatcher, _functions, value.hostFunction.signature,
id: value.hostFunction.id));
case Value_Value.calculation:
return _deprotofyCalculation(value.calculation);
case Value_Value.singleton:
return switch (value.singleton) {
SingletonValue.TRUE => sassTrue,
SingletonValue.FALSE => sassFalse,
SingletonValue.NULL => sassNull,
_ => throw "Unknown Value.singleton ${value.singleton}"
};
case Value_Value.notSet:
throw mandatoryError("Value.value");
}
} on RangeError catch (error) {
var name = error.name;
if (name == null || error.start == null || error.end == null) {
throw paramsError(error.toString());
}
if (value.whichValue() == Value_Value.rgbColor) {
name = 'RgbColor.$name';
} else if (value.whichValue() == Value_Value.hslColor) {
name = 'HslColor.$name';
}
throw paramsError(
'$name must be between ${error.start} and ${error.end}, was '
'${error.invalidValue}');
}
}
/// Converts [number] to its Sass representation.
SassNumber _deprotofyNumber(Value_Number number) =>
SassNumber.withUnits(number.value,
numeratorUnits: number.numerators,
denominatorUnits: number.denominators);
/// Returns the argument list in [_argumentLists] that corresponds to [id].
SassArgumentList _argumentListForId(int id) {
if (id < 1) {
throw paramsError(
"Value.ArgumentList.id $id can't be marked as accessed");
} else if (id > _argumentLists.length) {
throw paramsError(
"Value.ArgumentList.id $id doesn't match any known argument "
"lists");
} else {
return _argumentLists[id - 1];
}
}
/// Converts [separator] to its Sass representation.
ListSeparator _deprotofySeparator(proto.ListSeparator separator) =>
switch (separator) {
proto.ListSeparator.COMMA => ListSeparator.comma,
proto.ListSeparator.SPACE => ListSeparator.space,
proto.ListSeparator.SLASH => ListSeparator.slash,
proto.ListSeparator.UNDECIDED => ListSeparator.undecided,
_ => throw "Unknown ListSeparator $separator",
};
/// Converts [calculation] to its Sass representation.
Value _deprotofyCalculation(Value_Calculation calculation) =>
switch (calculation) {
Value_Calculation(name: "calc", arguments: [var arg]) =>
SassCalculation.calc(_deprotofyCalculationValue(arg)),
Value_Calculation(name: "calc") => throw paramsError(
"Value.Calculation.arguments must have exactly one argument for "
"calc()."),
Value_Calculation(
name: "clamp",
arguments: [var arg1, ...var rest] && List(length: < 4)
) =>
SassCalculation.clamp(
_deprotofyCalculationValue(arg1),
rest.elementAtOrNull(0).andThen(_deprotofyCalculationValue),
rest.elementAtOrNull(1).andThen(_deprotofyCalculationValue)),
Value_Calculation(name: "clamp") => throw paramsError(
"Value.Calculation.arguments must have 1 to 3 arguments for "
"clamp()."),
Value_Calculation(name: "min" || "max", arguments: []) =>
throw paramsError(
"Value.Calculation.arguments must have at least 1 argument for "
"${calculation.name}()."),
Value_Calculation(name: "min", :var arguments) =>
SassCalculation.min(arguments.map(_deprotofyCalculationValue)),
Value_Calculation(name: "max", :var arguments) =>
SassCalculation.max(arguments.map(_deprotofyCalculationValue)),
_ => throw paramsError(
'Value.Calculation.name "${calculation.name}" is not a recognized '
'calculation type.')
};
/// Converts [value] to its Sass representation.
Object _deprotofyCalculationValue(Value_Calculation_CalculationValue value) =>
switch (value.whichValue()) {
Value_Calculation_CalculationValue_Value.number =>
_deprotofyNumber(value.number),
Value_Calculation_CalculationValue_Value.calculation =>
_deprotofyCalculation(value.calculation),
Value_Calculation_CalculationValue_Value.string =>
SassString(value.string, quotes: false),
Value_Calculation_CalculationValue_Value.operation =>
SassCalculation.operate(
_deprotofyCalculationOperator(value.operation.operator),
_deprotofyCalculationValue(value.operation.left),
_deprotofyCalculationValue(value.operation.right)),
Value_Calculation_CalculationValue_Value.interpolation =>
SassString('(${value.interpolation})', quotes: false),
Value_Calculation_CalculationValue_Value.notSet =>
throw mandatoryError("Value.Calculation.value")
};
/// Converts [operator] to its Sass representation.
CalculationOperator _deprotofyCalculationOperator(
proto.CalculationOperator operator) =>
switch (operator) {
proto.CalculationOperator.PLUS => CalculationOperator.plus,
proto.CalculationOperator.MINUS => CalculationOperator.minus,
proto.CalculationOperator.TIMES => CalculationOperator.times,
proto.CalculationOperator.DIVIDE => CalculationOperator.dividedBy,
_ => throw "Unknown CalculationOperator $operator"
};
}