/
built_in.dart
122 lines (105 loc) · 4.53 KB
/
built_in.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
// Copyright 2016 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 'package:tuple/tuple.dart';
import '../ast/sass.dart';
import '../callable.dart';
import '../value.dart';
typedef Callback = Value Function(List<Value> arguments);
/// A callable defined in Dart code.
///
/// Unlike user-defined callables, built-in callables support overloads. They
/// may declare multiple different callbacks with multiple different sets of
/// arguments. When the callable is invoked, the first callback with matching
/// arguments is invoked.
class BuiltInCallable implements Callable, AsyncBuiltInCallable {
final String name;
/// The overloads declared for this callable.
final List<Tuple2<ArgumentDeclaration, Callback>> _overloads;
/// Creates a function with a single [arguments] declaration and a single
/// [callback].
///
/// The argument declaration is parsed from [arguments], which should not
/// include parentheses. Throws a [SassFormatException] if parsing fails.
///
/// If passed, [url] is the URL of the module in which the function is
/// defined.
BuiltInCallable.function(
String name, String arguments, Value callback(List<Value> arguments),
{Object? url})
: this.parsed(
name,
ArgumentDeclaration.parse('@function $name($arguments) {',
url: url),
callback);
/// Creates a mixin with a single [arguments] declaration and a single
/// [callback].
///
/// The argument declaration is parsed from [arguments], which should not
/// include parentheses. Throws a [SassFormatException] if parsing fails.
///
/// If passed, [url] is the URL of the module in which the mixin is
/// defined.
BuiltInCallable.mixin(
String name, String arguments, void callback(List<Value> arguments),
{Object? url})
: this.parsed(name,
ArgumentDeclaration.parse('@mixin $name($arguments) {', url: url),
(arguments) {
callback(arguments);
return sassNull;
});
/// Creates a callable with a single [arguments] declaration and a single
/// [callback].
BuiltInCallable.parsed(this.name, ArgumentDeclaration arguments,
Value callback(List<Value> arguments))
: _overloads = [Tuple2(arguments, callback)];
/// Creates a function with multiple implementations.
///
/// Each key/value pair in [overloads] defines the argument declaration for
/// the overload (which should not include parentheses), and the callback to
/// execute if that argument declaration matches. Throws a
/// [SassFormatException] if parsing fails.
///
/// If passed, [url] is the URL of the module in which the function is
/// defined.
BuiltInCallable.overloadedFunction(this.name, Map<String, Callback> overloads,
{Object? url})
: _overloads = [
for (var entry in overloads.entries)
Tuple2(
ArgumentDeclaration.parse('@function $name(${entry.key}) {',
url: url),
entry.value)
];
BuiltInCallable._(this.name, this._overloads);
/// Returns the argument declaration and Dart callback for the given
/// positional and named arguments.
///
/// If no exact match is found, finds the closest approximation. Note that this
/// doesn't guarantee that [positional] and [names] are valid for the returned
/// [ArgumentDeclaration].
Tuple2<ArgumentDeclaration, Callback> callbackFor(
int positional, Set<String> names) {
Tuple2<ArgumentDeclaration, Callback>? fuzzyMatch;
int? minMismatchDistance;
for (var overload in _overloads) {
// Ideally, find an exact match.
if (overload.item1.matches(positional, names)) return overload;
var mismatchDistance = overload.item1.arguments.length - positional;
if (minMismatchDistance != null) {
if (mismatchDistance.abs() > minMismatchDistance.abs()) continue;
// If two overloads have the same mismatch distance, favor the overload
// that has more arguments.
if (mismatchDistance.abs() == minMismatchDistance.abs() &&
mismatchDistance < 0) continue;
}
minMismatchDistance = mismatchDistance;
fuzzyMatch = overload;
}
if (fuzzyMatch != null) return fuzzyMatch;
throw StateError("BuiltInCallable $name may not have empty overloads.");
}
/// Returns a copy of this callable with the given [name].
BuiltInCallable withName(String name) => BuiltInCallable._(name, _overloads);
}