@mapwhit/tilerenderer 0.47.2 → 0.48.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/build/min/package.json +1 -1
- package/package.json +1 -1
- package/src/data/bucket/symbol_bucket.js +25 -11
- package/src/data/program_configuration.js +18 -10
- package/src/geo/transform.js +4 -2
- package/src/index.js +1 -1
- package/src/source/rtl_text_plugin.js +1 -0
- package/src/source/source_cache.js +1 -1
- package/src/source/tile.js +6 -5
- package/src/source/worker.js +1 -10
- package/src/style/style.js +4 -19
- package/src/style/style_layer/symbol_style_layer_properties.js +1 -1
- package/src/style-spec/expression/compound_expression.js +30 -16
- package/src/style-spec/expression/definitions/coercion.js +13 -0
- package/src/style-spec/expression/definitions/comparison.js +193 -0
- package/src/style-spec/expression/definitions/formatted.js +123 -0
- package/src/style-spec/expression/definitions/index.js +10 -60
- package/src/style-spec/expression/definitions/interpolate.js +17 -7
- package/src/style-spec/expression/definitions/literal.js +5 -0
- package/src/style-spec/expression/parsing_context.js +4 -0
- package/src/style-spec/expression/types.js +12 -1
- package/src/style-spec/feature_filter/index.js +1 -1
- package/src/style-spec/reference/v8.json +118 -47
- package/src/symbol/get_anchors.js +11 -22
- package/src/symbol/mergelines.js +4 -1
- package/src/symbol/placement.js +8 -2
- package/src/symbol/quads.js +7 -6
- package/src/symbol/shaping.js +185 -40
- package/src/symbol/symbol_layout.js +9 -6
- package/src/symbol/transform_text.js +12 -1
- package/src/ui/map.js +8 -25
- package/src/style-spec/expression/definitions/equals.js +0 -93
package/build/min/package.json
CHANGED
package/package.json
CHANGED
|
@@ -31,6 +31,7 @@ const { verticalizedCharacterMap } = require('../../util/verticalize_punctuation
|
|
|
31
31
|
const { getSizeData } = require('../../symbol/symbol_size');
|
|
32
32
|
const { register } = require('../../util/transfer_registry');
|
|
33
33
|
const EvaluationParameters = require('../../style/evaluation_parameters');
|
|
34
|
+
const { Formatted } = require('../../style-spec/expression/definitions/formatted');
|
|
34
35
|
|
|
35
36
|
// Opacity arrays are frequently updated but don't contain a lot of information, so we pack them
|
|
36
37
|
// tight. Each Uint32 is actually four duplicate Uint8s for the four corners of a glyph
|
|
@@ -221,6 +222,18 @@ class SymbolBucket {
|
|
|
221
222
|
this.lineVertexArray = new SymbolLineVertexArray();
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
calculateGlyphDependencies(text, stack, textAlongLine, doesAllowVerticalWritingMode) {
|
|
226
|
+
for (let i = 0; i < text.length; i++) {
|
|
227
|
+
stack[text.charCodeAt(i)] = true;
|
|
228
|
+
if (textAlongLine && doesAllowVerticalWritingMode) {
|
|
229
|
+
const verticalChar = verticalizedCharacterMap[text.charAt(i)];
|
|
230
|
+
if (verticalChar) {
|
|
231
|
+
stack[verticalChar.charCodeAt(0)] = true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
224
237
|
populate(features, options) {
|
|
225
238
|
const layer = this.layers[0];
|
|
226
239
|
const layout = layer.layout;
|
|
@@ -229,7 +242,7 @@ class SymbolBucket {
|
|
|
229
242
|
const textField = layout.get('text-field');
|
|
230
243
|
const iconImage = layout.get('icon-image');
|
|
231
244
|
const hasText =
|
|
232
|
-
(textField.value.kind !== 'constant' || textField.value.value.length > 0) &&
|
|
245
|
+
(textField.value.kind !== 'constant' || textField.value.value.toString().length > 0) &&
|
|
233
246
|
(textFont.value.kind !== 'constant' || textFont.value.value.length > 0);
|
|
234
247
|
const hasIcon = iconImage.value.kind !== 'constant' || (iconImage.value.value && iconImage.value.value.length > 0);
|
|
235
248
|
|
|
@@ -286,15 +299,16 @@ class SymbolBucket {
|
|
|
286
299
|
const stack = (stacks[fontStack] = stacks[fontStack] || {});
|
|
287
300
|
const textAlongLine =
|
|
288
301
|
layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point';
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
stack[verticalChar.charCodeAt(0)] = true;
|
|
296
|
-
}
|
|
302
|
+
if (text instanceof Formatted) {
|
|
303
|
+
for (const section of text.sections) {
|
|
304
|
+
const doesAllowVerticalWritingMode = allowsVerticalWritingMode(text.toString());
|
|
305
|
+
const sectionFont = section.fontStack || fontStack;
|
|
306
|
+
const sectionStack = (stacks[sectionFont] = stacks[sectionFont] || {});
|
|
307
|
+
this.calculateGlyphDependencies(section.text, sectionStack, textAlongLine, doesAllowVerticalWritingMode);
|
|
297
308
|
}
|
|
309
|
+
} else {
|
|
310
|
+
const doesAllowVerticalWritingMode = allowsVerticalWritingMode(text);
|
|
311
|
+
this.calculateGlyphDependencies(text, stack, textAlongLine, doesAllowVerticalWritingMode);
|
|
298
312
|
}
|
|
299
313
|
}
|
|
300
314
|
}
|
|
@@ -642,8 +656,8 @@ class SymbolBucket {
|
|
|
642
656
|
symbolInstanceIndexes.sort((aIndex, bIndex) => {
|
|
643
657
|
const a = this.symbolInstances[aIndex];
|
|
644
658
|
const b = this.symbolInstances[bIndex];
|
|
645
|
-
const aRotated = (sin * a.anchor.x + cos * a.anchor.y) | 0;
|
|
646
|
-
const bRotated = (sin * b.anchor.x + cos * b.anchor.y) | 0;
|
|
659
|
+
const aRotated = Math.round(sin * a.anchor.x + cos * a.anchor.y) | 0;
|
|
660
|
+
const bRotated = Math.round(sin * b.anchor.x + cos * b.anchor.y) | 0;
|
|
647
661
|
return aRotated - bRotated || b.featureIndex - a.featureIndex;
|
|
648
662
|
});
|
|
649
663
|
|
|
@@ -170,11 +170,15 @@ class SourceExpressionBinder {
|
|
|
170
170
|
|
|
171
171
|
upload(context) {
|
|
172
172
|
if (this.paintVertexArray?.arrayBuffer) {
|
|
173
|
-
this.paintVertexBuffer
|
|
174
|
-
this.paintVertexArray
|
|
175
|
-
|
|
176
|
-
this.
|
|
177
|
-
|
|
173
|
+
if (this.paintVertexBuffer?.buffer) {
|
|
174
|
+
this.paintVertexBuffer.updateData(this.paintVertexArray);
|
|
175
|
+
} else {
|
|
176
|
+
this.paintVertexBuffer = context.createVertexBuffer(
|
|
177
|
+
this.paintVertexArray,
|
|
178
|
+
this.paintVertexAttributes,
|
|
179
|
+
this.expression.isStateDependent
|
|
180
|
+
);
|
|
181
|
+
}
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
|
|
@@ -265,11 +269,15 @@ class CompositeExpressionBinder {
|
|
|
265
269
|
|
|
266
270
|
upload(context) {
|
|
267
271
|
if (this.paintVertexArray?.arrayBuffer) {
|
|
268
|
-
this.paintVertexBuffer
|
|
269
|
-
this.paintVertexArray
|
|
270
|
-
|
|
271
|
-
this.
|
|
272
|
-
|
|
272
|
+
if (this.paintVertexBuffer?.buffer) {
|
|
273
|
+
this.paintVertexBuffer.updateData(this.paintVertexArray);
|
|
274
|
+
} else {
|
|
275
|
+
this.paintVertexBuffer = context.createVertexBuffer(
|
|
276
|
+
this.paintVertexArray,
|
|
277
|
+
this.paintVertexAttributes,
|
|
278
|
+
this.expression.isStateDependent
|
|
279
|
+
);
|
|
280
|
+
}
|
|
273
281
|
}
|
|
274
282
|
}
|
|
275
283
|
|
package/src/geo/transform.js
CHANGED
|
@@ -17,12 +17,13 @@ const { vec4, mat4, mat2 } = require('@mapbox/gl-matrix');
|
|
|
17
17
|
class Transform {
|
|
18
18
|
constructor(minZoom, maxZoom, renderWorldCopies) {
|
|
19
19
|
this.tileSize = 512; // constant
|
|
20
|
+
this.maxValidLatitude = 85.051129; // constant
|
|
20
21
|
|
|
21
22
|
this._renderWorldCopies = renderWorldCopies === undefined ? true : renderWorldCopies;
|
|
22
23
|
this._minZoom = minZoom || 0;
|
|
23
24
|
this._maxZoom = maxZoom || 22;
|
|
24
25
|
|
|
25
|
-
this.latRange = [-
|
|
26
|
+
this.latRange = [-this.maxValidLatitude, this.maxValidLatitude];
|
|
26
27
|
|
|
27
28
|
this.width = 0;
|
|
28
29
|
this.height = 0;
|
|
@@ -262,7 +263,7 @@ class Transform {
|
|
|
262
263
|
}
|
|
263
264
|
|
|
264
265
|
/**
|
|
265
|
-
*
|
|
266
|
+
* longitude to absolute x coord
|
|
266
267
|
* @returns {number} pixel coordinate
|
|
267
268
|
*/
|
|
268
269
|
lngX(lng) {
|
|
@@ -273,6 +274,7 @@ class Transform {
|
|
|
273
274
|
* @returns {number} pixel coordinate
|
|
274
275
|
*/
|
|
275
276
|
latY(lat) {
|
|
277
|
+
lat = clamp(lat, -this.maxValidLatitude, this.maxValidLatitude);
|
|
276
278
|
const y = (180 / Math.PI) * Math.log(Math.tan(Math.PI / 4 + (lat * Math.PI) / 360));
|
|
277
279
|
return ((180 - y) * this.worldSize) / 360;
|
|
278
280
|
}
|
package/src/index.js
CHANGED
|
@@ -43,7 +43,7 @@ module.exports = {
|
|
|
43
43
|
* @param {string} pluginURL URL pointing to the Mapbox RTL text plugin source.
|
|
44
44
|
* @param {Function} callback Called with an error argument if there is an error.
|
|
45
45
|
* @example
|
|
46
|
-
* mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.
|
|
46
|
+
* mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.0/mapbox-gl-rtl-text.js');
|
|
47
47
|
* @see [Add support for right-to-left scripts](https://www.mapbox.com/mapbox-gl-js/example/mapbox-gl-rtl-text/)
|
|
48
48
|
*/
|
|
49
49
|
|
|
@@ -47,6 +47,7 @@ function setRTLTextPlugin(url, callback) {
|
|
|
47
47
|
const plugin = {
|
|
48
48
|
applyArabicShaping: null,
|
|
49
49
|
processBidirectionalText: null,
|
|
50
|
+
processStyledBidirectionalText: null,
|
|
50
51
|
isLoaded: function () {
|
|
51
52
|
return (
|
|
52
53
|
foregroundLoadComplete || // Foreground: loaded if the completion callback returned successfully
|
package/src/source/tile.js
CHANGED
|
@@ -101,8 +101,9 @@ class Tile {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
this.queryPadding = 0;
|
|
104
|
-
for (const
|
|
105
|
-
|
|
104
|
+
for (const id in this.buckets) {
|
|
105
|
+
const bucket = this.buckets[id];
|
|
106
|
+
this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(id).queryRadius(bucket));
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
if (data.imageAtlas) {
|
|
@@ -297,8 +298,8 @@ class Tile {
|
|
|
297
298
|
|
|
298
299
|
const vtLayers = this.latestFeatureIndex.loadVTLayers();
|
|
299
300
|
|
|
300
|
-
for (const
|
|
301
|
-
const bucket = this.buckets[
|
|
301
|
+
for (const id in this.buckets) {
|
|
302
|
+
const bucket = this.buckets[id];
|
|
302
303
|
// Buckets are grouped by common source-layer
|
|
303
304
|
const sourceLayerId = bucket.layers[0]['sourceLayer'] || '_geojsonTileLayer';
|
|
304
305
|
const sourceLayer = vtLayers[sourceLayerId];
|
|
@@ -307,7 +308,7 @@ class Tile {
|
|
|
307
308
|
|
|
308
309
|
bucket.update(sourceLayerStates, sourceLayer, this.imageAtlas?.patternPositions || {});
|
|
309
310
|
if (painter?.style) {
|
|
310
|
-
this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(
|
|
311
|
+
this.queryPadding = Math.max(this.queryPadding, painter.style.getLayer(id).queryRadius(bucket));
|
|
311
312
|
}
|
|
312
313
|
}
|
|
313
314
|
}
|
package/src/source/worker.js
CHANGED
|
@@ -38,6 +38,7 @@ class Worker {
|
|
|
38
38
|
}
|
|
39
39
|
globalRTLTextPlugin['applyArabicShaping'] = rtlTextPlugin.applyArabicShaping;
|
|
40
40
|
globalRTLTextPlugin['processBidirectionalText'] = rtlTextPlugin.processBidirectionalText;
|
|
41
|
+
globalRTLTextPlugin['processStyledBidirectionalText'] = rtlTextPlugin.processStyledBidirectionalText;
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -71,16 +72,6 @@ class Worker {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
/**
|
|
75
|
-
* Load a {@link WorkerSource} script at params.url. The script is run
|
|
76
|
-
* (using importScripts) with `registerWorkerSource` in scope, which is a
|
|
77
|
-
* function taking `(name, workerSourceObject)`.
|
|
78
|
-
* @private
|
|
79
|
-
*/
|
|
80
|
-
loadWorkerSource(map, params) {
|
|
81
|
-
this.self.importScripts(params.url);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
75
|
loadRTLTextPlugin(map, pluginURL) {
|
|
85
76
|
if (!globalRTLTextPlugin.isLoaded()) {
|
|
86
77
|
this.self.importScripts(pluginURL);
|
package/src/style/style.js
CHANGED
|
@@ -633,6 +633,10 @@ class Style extends Evented {
|
|
|
633
633
|
this.fire(new ErrorEvent(new Error('The sourceLayer parameter must be provided for vector source types.')));
|
|
634
634
|
return;
|
|
635
635
|
}
|
|
636
|
+
if (feature.id == null || feature.id === '') {
|
|
637
|
+
this.fire(new ErrorEvent(new Error('The feature id parameter must be provided.')));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
636
640
|
|
|
637
641
|
sourceCache.setFeatureState(sourceLayer, feature.id, state);
|
|
638
642
|
}
|
|
@@ -767,25 +771,6 @@ class Style extends Evented {
|
|
|
767
771
|
return sourceCache ? querySourceFeatures(sourceCache, params) : [];
|
|
768
772
|
}
|
|
769
773
|
|
|
770
|
-
addSourceType(name, SourceType, callback) {
|
|
771
|
-
if (Style.getSourceType(name)) {
|
|
772
|
-
return callback(new Error(`A source type called "${name}" already exists.`));
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
Style.setSourceType(name, SourceType);
|
|
776
|
-
|
|
777
|
-
if (!SourceType.workerSourceURL) {
|
|
778
|
-
return callback(null, null);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
this.dispatcher
|
|
782
|
-
.broadcast('loadWorkerSource', {
|
|
783
|
-
name: name,
|
|
784
|
-
url: SourceType.workerSourceURL
|
|
785
|
-
})
|
|
786
|
-
.then(data => callback(null, data), callback);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
774
|
getLight() {
|
|
790
775
|
return this.light.getLight();
|
|
791
776
|
}
|
|
@@ -106,7 +106,7 @@ const layout = new Properties({
|
|
|
106
106
|
expression: { parameters: ['zoom'] }
|
|
107
107
|
}),
|
|
108
108
|
'text-field': new DataDrivenProperty({
|
|
109
|
-
type: '
|
|
109
|
+
type: 'formatted',
|
|
110
110
|
default: '',
|
|
111
111
|
tokens: true,
|
|
112
112
|
expression: { parameters: ['zoom', 'feature'] }
|
|
@@ -46,20 +46,6 @@ class CompoundExpression {
|
|
|
46
46
|
signature.length === args.length - 1 // correct param count
|
|
47
47
|
);
|
|
48
48
|
|
|
49
|
-
// First parse all the args
|
|
50
|
-
const parsedArgs = [];
|
|
51
|
-
for (let i = 1; i < args.length; i++) {
|
|
52
|
-
const arg = args[i];
|
|
53
|
-
let expected;
|
|
54
|
-
if (overloads.length === 1) {
|
|
55
|
-
const params = overloads[0][0];
|
|
56
|
-
expected = Array.isArray(params) ? params[i - 1] : params.type;
|
|
57
|
-
}
|
|
58
|
-
const parsed = context.parse(arg, 1 + parsedArgs.length, expected);
|
|
59
|
-
if (!parsed) return null;
|
|
60
|
-
parsedArgs.push(parsed);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
49
|
let signatureContext = null;
|
|
64
50
|
|
|
65
51
|
for (const [params, evaluate] of overloads) {
|
|
@@ -67,6 +53,27 @@ class CompoundExpression {
|
|
|
67
53
|
// we eventually succeed, we haven't polluted `context.errors`.
|
|
68
54
|
signatureContext = new ParsingContext(context.registry, context.path, null, context.scope);
|
|
69
55
|
|
|
56
|
+
// First parse all the args, potentially coercing to the
|
|
57
|
+
// types expected by this overload.
|
|
58
|
+
const parsedArgs = [];
|
|
59
|
+
let argParseFailed = false;
|
|
60
|
+
for (let i = 1; i < args.length; i++) {
|
|
61
|
+
const arg = args[i];
|
|
62
|
+
const expectedType = Array.isArray(params) ? params[i - 1] : params.type;
|
|
63
|
+
|
|
64
|
+
const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType);
|
|
65
|
+
if (!parsed) {
|
|
66
|
+
argParseFailed = true;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
parsedArgs.push(parsed);
|
|
70
|
+
}
|
|
71
|
+
if (argParseFailed) {
|
|
72
|
+
// Couldn't coerce args of this overload to expected type, move
|
|
73
|
+
// on to next one.
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
if (Array.isArray(params)) {
|
|
71
78
|
if (params.length !== parsedArgs.length) {
|
|
72
79
|
signatureContext.error(`Expected ${params.length} arguments, but found ${parsedArgs.length} instead.`);
|
|
@@ -92,8 +99,15 @@ class CompoundExpression {
|
|
|
92
99
|
} else {
|
|
93
100
|
const expected = overloads.length ? overloads : availableOverloads;
|
|
94
101
|
const signatures = expected.map(([params]) => stringifySignature(params)).join(' | ');
|
|
95
|
-
const actualTypes =
|
|
96
|
-
|
|
102
|
+
const actualTypes = [];
|
|
103
|
+
// For error message, re-parse arguments without trying to
|
|
104
|
+
// apply any coercions
|
|
105
|
+
for (let i = 1; i < args.length; i++) {
|
|
106
|
+
const parsed = context.parse(args[i], 1 + actualTypes.length);
|
|
107
|
+
if (!parsed) return null;
|
|
108
|
+
actualTypes.push(toString(parsed.type));
|
|
109
|
+
}
|
|
110
|
+
context.error(`Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.`);
|
|
97
111
|
}
|
|
98
112
|
|
|
99
113
|
return null;
|
|
@@ -3,6 +3,7 @@ const assert = require('assert');
|
|
|
3
3
|
const { ColorType, ValueType, NumberType } = require('../types');
|
|
4
4
|
const { Color, validateRGBA } = require('../values');
|
|
5
5
|
const RuntimeError = require('../runtime_error');
|
|
6
|
+
const { Formatted, FormattedSection } = require('./formatted');
|
|
6
7
|
|
|
7
8
|
const types = {
|
|
8
9
|
'to-number': NumberType,
|
|
@@ -65,6 +66,18 @@ class Coercion {
|
|
|
65
66
|
error || `Could not parse color from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`
|
|
66
67
|
);
|
|
67
68
|
}
|
|
69
|
+
if (this.type.kind === 'formatted') {
|
|
70
|
+
let input;
|
|
71
|
+
for (const arg of this.args) {
|
|
72
|
+
input = arg.evaluate(ctx);
|
|
73
|
+
if (typeof input === 'string') {
|
|
74
|
+
return new Formatted([new FormattedSection(input, null, null)]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
throw new RuntimeError(
|
|
78
|
+
`Could not parse formatted text from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
68
81
|
let value = null;
|
|
69
82
|
for (const arg of this.args) {
|
|
70
83
|
value = arg.evaluate(ctx);
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const { toString, ValueType, BooleanType, CollatorType } = require('../types');
|
|
2
|
+
const Assertion = require('./assertion');
|
|
3
|
+
const { typeOf } = require('../values');
|
|
4
|
+
const RuntimeError = require('../runtime_error');
|
|
5
|
+
|
|
6
|
+
function isComparableType(op, type) {
|
|
7
|
+
if (op === '==' || op === '!=') {
|
|
8
|
+
// equality operator
|
|
9
|
+
return (
|
|
10
|
+
type.kind === 'boolean' ||
|
|
11
|
+
type.kind === 'string' ||
|
|
12
|
+
type.kind === 'number' ||
|
|
13
|
+
type.kind === 'null' ||
|
|
14
|
+
type.kind === 'value'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
// ordering operator
|
|
18
|
+
return type.kind === 'string' || type.kind === 'number' || type.kind === 'value';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function eq(ctx, a, b) {
|
|
22
|
+
return a === b;
|
|
23
|
+
}
|
|
24
|
+
function neq(ctx, a, b) {
|
|
25
|
+
return a !== b;
|
|
26
|
+
}
|
|
27
|
+
function lt(ctx, a, b) {
|
|
28
|
+
return a < b;
|
|
29
|
+
}
|
|
30
|
+
function gt(ctx, a, b) {
|
|
31
|
+
return a > b;
|
|
32
|
+
}
|
|
33
|
+
function lteq(ctx, a, b) {
|
|
34
|
+
return a <= b;
|
|
35
|
+
}
|
|
36
|
+
function gteq(ctx, a, b) {
|
|
37
|
+
return a >= b;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function eqCollate(ctx, a, b, c) {
|
|
41
|
+
return c.compare(a, b) === 0;
|
|
42
|
+
}
|
|
43
|
+
function neqCollate(ctx, a, b, c) {
|
|
44
|
+
return !eqCollate(ctx, a, b, c);
|
|
45
|
+
}
|
|
46
|
+
function ltCollate(ctx, a, b, c) {
|
|
47
|
+
return c.compare(a, b) < 0;
|
|
48
|
+
}
|
|
49
|
+
function gtCollate(ctx, a, b, c) {
|
|
50
|
+
return c.compare(a, b) > 0;
|
|
51
|
+
}
|
|
52
|
+
function lteqCollate(ctx, a, b, c) {
|
|
53
|
+
return c.compare(a, b) <= 0;
|
|
54
|
+
}
|
|
55
|
+
function gteqCollate(ctx, a, b, c) {
|
|
56
|
+
return c.compare(a, b) >= 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Special form for comparison operators, implementing the signatures:
|
|
61
|
+
* - (T, T, ?Collator) => boolean
|
|
62
|
+
* - (T, value, ?Collator) => boolean
|
|
63
|
+
* - (value, T, ?Collator) => boolean
|
|
64
|
+
*
|
|
65
|
+
* For inequalities, T must be either value, string, or number. For ==/!=, it
|
|
66
|
+
* can also be boolean or null.
|
|
67
|
+
*
|
|
68
|
+
* Equality semantics are equivalent to Javascript's strict equality (===/!==)
|
|
69
|
+
* -- i.e., when the arguments' types don't match, == evaluates to false, != to
|
|
70
|
+
* true.
|
|
71
|
+
*
|
|
72
|
+
* When types don't match in an ordering comparison, a runtime error is thrown.
|
|
73
|
+
*
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
function makeComparison(op, compareBasic, compareWithCollator) {
|
|
77
|
+
const isOrderComparison = op !== '==' && op !== '!=';
|
|
78
|
+
|
|
79
|
+
return class Comparison {
|
|
80
|
+
constructor(lhs, rhs, collator) {
|
|
81
|
+
this.type = BooleanType;
|
|
82
|
+
this.lhs = lhs;
|
|
83
|
+
this.rhs = rhs;
|
|
84
|
+
this.collator = collator;
|
|
85
|
+
this.hasUntypedArgument = lhs.type.kind === 'value' || rhs.type.kind === 'value';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static parse(args, context) {
|
|
89
|
+
if (args.length !== 3 && args.length !== 4) return context.error('Expected two or three arguments.');
|
|
90
|
+
|
|
91
|
+
const op = args[0];
|
|
92
|
+
|
|
93
|
+
let lhs = context.parse(args[1], 1, ValueType);
|
|
94
|
+
if (!lhs) return null;
|
|
95
|
+
if (!isComparableType(op, lhs.type)) {
|
|
96
|
+
return context.concat(1).error(`"${op}" comparisons are not supported for type '${toString(lhs.type)}'.`);
|
|
97
|
+
}
|
|
98
|
+
let rhs = context.parse(args[2], 2, ValueType);
|
|
99
|
+
if (!rhs) return null;
|
|
100
|
+
if (!isComparableType(op, rhs.type)) {
|
|
101
|
+
return context.concat(2).error(`"${op}" comparisons are not supported for type '${toString(rhs.type)}'.`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (lhs.type.kind !== rhs.type.kind && lhs.type.kind !== 'value' && rhs.type.kind !== 'value') {
|
|
105
|
+
return context.error(`Cannot compare types '${toString(lhs.type)}' and '${toString(rhs.type)}'.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isOrderComparison) {
|
|
109
|
+
// typing rules specific to less/greater than operators
|
|
110
|
+
if (lhs.type.kind === 'value' && rhs.type.kind !== 'value') {
|
|
111
|
+
// (value, T)
|
|
112
|
+
lhs = new Assertion(rhs.type, [lhs]);
|
|
113
|
+
} else if (lhs.type.kind !== 'value' && rhs.type.kind === 'value') {
|
|
114
|
+
// (T, value)
|
|
115
|
+
rhs = new Assertion(lhs.type, [rhs]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let collator = null;
|
|
120
|
+
if (args.length === 4) {
|
|
121
|
+
if (
|
|
122
|
+
lhs.type.kind !== 'string' &&
|
|
123
|
+
rhs.type.kind !== 'string' &&
|
|
124
|
+
lhs.type.kind !== 'value' &&
|
|
125
|
+
rhs.type.kind !== 'value'
|
|
126
|
+
) {
|
|
127
|
+
return context.error('Cannot use collator to compare non-string types.');
|
|
128
|
+
}
|
|
129
|
+
collator = context.parse(args[3], 3, CollatorType);
|
|
130
|
+
if (!collator) return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return new Comparison(lhs, rhs, collator);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
evaluate(ctx) {
|
|
137
|
+
const lhs = this.lhs.evaluate(ctx);
|
|
138
|
+
const rhs = this.rhs.evaluate(ctx);
|
|
139
|
+
|
|
140
|
+
if (isOrderComparison && this.hasUntypedArgument) {
|
|
141
|
+
const lt = typeOf(lhs);
|
|
142
|
+
const rt = typeOf(rhs);
|
|
143
|
+
// check that type is string or number, and equal
|
|
144
|
+
if (lt.kind !== rt.kind || !(lt.kind === 'string' || lt.kind === 'number')) {
|
|
145
|
+
throw new RuntimeError(
|
|
146
|
+
`Expected arguments for "${op}" to be (string, string) or (number, number), but found (${lt.kind}, ${rt.kind}) instead.`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (this.collator && !isOrderComparison && this.hasUntypedArgument) {
|
|
152
|
+
const lt = typeOf(lhs);
|
|
153
|
+
const rt = typeOf(rhs);
|
|
154
|
+
if (lt.kind !== 'string' || rt.kind !== 'string') {
|
|
155
|
+
return compareBasic(ctx, lhs, rhs);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return this.collator
|
|
160
|
+
? compareWithCollator(ctx, lhs, rhs, this.collator.evaluate(ctx))
|
|
161
|
+
: compareBasic(ctx, lhs, rhs);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
eachChild(fn) {
|
|
165
|
+
fn(this.lhs);
|
|
166
|
+
fn(this.rhs);
|
|
167
|
+
if (this.collator) {
|
|
168
|
+
fn(this.collator);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
possibleOutputs() {
|
|
173
|
+
return [true, false];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
serialize() {
|
|
177
|
+
const serialized = [op];
|
|
178
|
+
this.eachChild(child => {
|
|
179
|
+
serialized.push(child.serialize());
|
|
180
|
+
});
|
|
181
|
+
return serialized;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
Equals: makeComparison('==', eq, eqCollate),
|
|
188
|
+
NotEquals: makeComparison('!=', neq, neqCollate),
|
|
189
|
+
LessThan: makeComparison('<', lt, ltCollate),
|
|
190
|
+
GreaterThan: makeComparison('>', gt, gtCollate),
|
|
191
|
+
LessThanOrEqual: makeComparison('<=', lteq, lteqCollate),
|
|
192
|
+
GreaterThanOrEqual: makeComparison('>=', gteq, gteqCollate)
|
|
193
|
+
};
|