@@ -20,6 +20,157 @@ typedef DynamicMap = Map<String, Object?>;
2020/// Part of the data type for [DynamicContent] objects.
2121typedef DynamicList = List <Object ?>;
2222
23+ /// Reference to a location in a source file.
24+ ///
25+ /// This is used in a [SourceRange] object to indicate the location of a
26+ /// [BlobNode] in the original source text.
27+ ///
28+ /// Locations are given as offsets (in UTF-16 code units) into the decoded
29+ /// string.
30+ ///
31+ /// See also:
32+ ///
33+ /// * [BlobNode.source] , which exposes the source location of a [BlobNode] .
34+ @immutable
35+ class SourceLocation implements Comparable <SourceLocation > {
36+ /// Create a [SourceLocation] object.
37+ ///
38+ /// The [source] and [offset] properties are initialized from the
39+ /// given arguments.
40+ const SourceLocation (this .source, this .offset);
41+
42+ /// An object that identifies the file or other origin of the source.
43+ ///
44+ /// For files parsed using [parseLibraryFile] , this is the value that
45+ /// is given as the `sourceIdentifier` argument.
46+ final Object source;
47+
48+ /// The offset of the given source location, in UTF-16 code units.
49+ final int offset;
50+
51+ @override
52+ int compareTo (SourceLocation other) {
53+ if (source != other.source) {
54+ throw StateError ('Cannot compare locations from different sources.' );
55+ }
56+ return offset - other.offset;
57+ }
58+
59+ @override
60+ bool operator == (Object other) {
61+ if (other.runtimeType != SourceLocation ) {
62+ return false ;
63+ }
64+ return other is SourceLocation
65+ && source == other.source
66+ && offset == other.offset;
67+ }
68+
69+ @override
70+ int get hashCode => Object .hash (source, offset);
71+
72+ /// Whether this location is earlier in the file than `other` .
73+ ///
74+ /// Can only be used to compare locations in the same [source] .
75+ bool operator < (SourceLocation other) {
76+ if (source != other.source) {
77+ throw StateError ('Cannot compare locations from different sources.' );
78+ }
79+ return offset < other.offset;
80+ }
81+
82+ /// Whether this location is later in the file than `other` .
83+ ///
84+ /// Can only be used to compare locations in the same [source] .
85+ bool operator > (SourceLocation other) {
86+ if (source != other.source) {
87+ throw StateError ('Cannot compare locations from different sources.' );
88+ }
89+ return offset > other.offset;
90+ }
91+
92+ /// Whether this location is earlier in the file than `other` , or equal to
93+ /// `other` .
94+ ///
95+ /// Can only be used to compare locations in the same [source] .
96+ bool operator <= (SourceLocation other) {
97+ if (source != other.source) {
98+ throw StateError ('Cannot compare locations from different sources.' );
99+ }
100+ return offset <= other.offset;
101+ }
102+
103+ /// Whether this location is later in the file than `other` , or equal to
104+ /// `other` .
105+ ///
106+ /// Can only be used to compare locations in the same [source] .
107+ bool operator >= (SourceLocation other) {
108+ if (source != other.source) {
109+ throw StateError ('Cannot compare locations from different sources.' );
110+ }
111+ return offset >= other.offset;
112+ }
113+
114+ @override
115+ String toString () {
116+ return '$source @$offset ' ;
117+ }
118+ }
119+
120+ /// Reference to a range of a source file.
121+ ///
122+ /// This is used to indicate the region of a source file that corresponds to a
123+ /// particular [BlobNode] .
124+ ///
125+ /// By default, [BlobNode] s are not associated with [SourceRange] s. Source
126+ /// location information can be enabled for the [parseLibraryFile] parser by
127+ /// providing the `sourceIdentifier` argument.
128+ ///
129+ /// See also:
130+ ///
131+ /// * [BlobNode.source] , which exposes the source location of a [BlobNode] .
132+ @immutable
133+ class SourceRange {
134+ /// Create a [SourceRange] object.
135+ ///
136+ /// The [start] and [end] locations are initialized from the given arguments.
137+ ///
138+ /// They must have identical [SourceLocation.source] objects.
139+ SourceRange (this .start, this .end)
140+ : assert (start.source == end.source, 'The start and end locations have inconsistent source information.' ),
141+ assert (start < end, 'The start location must be before the end location.' );
142+
143+ /// The start of a contiguous region of a source file that corresponds to a
144+ /// particular [BlobNode] .
145+ ///
146+ /// The range contains the start.
147+ final SourceLocation start;
148+
149+ /// The end of a contiguous region of a source file that corresponds to a
150+ /// particular [BlobNode] .
151+ ///
152+ /// The range does not contain the end.
153+ final SourceLocation end;
154+
155+ @override
156+ bool operator == (Object other) {
157+ if (other.runtimeType != SourceRange ) {
158+ return false ;
159+ }
160+ return other is SourceRange
161+ && start == other.start
162+ && end == other.end;
163+ }
164+
165+ @override
166+ int get hashCode => Object .hash (start, end);
167+
168+ @override
169+ String toString () {
170+ return '${start .source }@${start .offset }..${end .offset }' ;
171+ }
172+ }
173+
23174/// Base class of nodes that appear in the output of [decodeDataBlob] and
24175/// [decodeLibraryBlob] .
25176///
@@ -35,6 +186,59 @@ abstract class BlobNode {
35186 /// Abstract const constructor. This constructor enables subclasses to provide
36187 /// const constructors so that they can be used in const expressions.
37188 const BlobNode ();
189+
190+ // We use an [Expando] so that there is no (or minimal) overhead in production
191+ // environments that don't need to track source locations. It would be cleaner
192+ // to store the information directly on the [BlobNode], as then we could enforce
193+ // that that information is always propagated, instead of relying on remembering
194+ // to do so. However, that would require growing the size of every [BlobNode]
195+ // object, and would require additional logic even in the binary parser (which
196+ // does not track source locations currently).
197+ static final Expando <SourceRange > _sources = Expando <SourceRange >('BlobNode._sources' );
198+
199+ /// The source location that corresponds to this [BlobNode] , if known.
200+ ///
201+ /// In normal use, this returns null. However, if source location tracking is
202+ /// enabled (e.g. by specifying the `sourceIdentifier` argument to
203+ /// [parseLibraryFile] ), then this will return the range of the source file
204+ /// that corresponds to this [BlobNode] .
205+ ///
206+ /// A [BlobNode] can also be manually associated with a given [SourceRange]
207+ /// using [associateSource] or [propagateSource] .
208+ SourceRange ? get source {
209+ return _sources[this ];
210+ }
211+
212+ /// Assign a [SourceRange] to this [BlobNode] 's [source] property.
213+ ///
214+ /// Typically, this is used exclusively by the parser (notably,
215+ /// [parseLibraryFile] ).
216+ ///
217+ /// Tracking source location information introduces a memory overhead and
218+ /// should therefore only be used when necessary (e.g. for creating IDEs).
219+ ///
220+ /// Calling this method replaces any existing association.
221+ void associateSource (SourceRange source) {
222+ _sources[this ] = source;
223+ }
224+
225+ /// Assign another [BlobNode] 's [SourceRange] to this [BlobNode] 's [source]
226+ /// property.
227+ ///
228+ /// Typically, this is used exclusively by the [Runtime] .
229+ ///
230+ /// If the `original` [BlobNode] is null or has no [source] , then this has no
231+ /// effect. Otherwise, the [source] for this [BlobNode] is set to match that
232+ /// of the given `original` [BlobNode] , replacing any existing association.
233+ void propagateSource (BlobNode ? original) {
234+ if (original == null ) {
235+ return ;
236+ }
237+ final SourceRange ? source = _sources[original];
238+ if (source != null ) {
239+ _sources[this ] = source;
240+ }
241+ }
38242}
39243
40244bool _listEquals <T >(List <T >? a, List <T >? b) {
@@ -192,7 +396,9 @@ class Switch extends BlobNode {
192396 /// [Switch] is an error.
193397 const Switch (this .input, this .outputs);
194398
195- /// The value to switch on (after resolution).
399+ /// The value to switch on. This is typically a reference, e.g. an
400+ /// [ArgsReference] , which must be resolved by the runtime to determine the
401+ /// actual value on which to switch.
196402 final Object input;
197403
198404 /// The cases for this switch. Keys correspond to values to compare with
0 commit comments