Shader Builder / add type checking in expressions parsing
All expressions will now be type checked before actually being parsed.
This commit is contained in:
@@ -120,6 +120,93 @@ export function isValueTypeColor(value) {
|
|||||||
return getValueType(value) === ValueTypes.COLOR || getValueType(value) === ValueTypes.COLOR_OR_STRING;
|
return getValueType(value) === ValueTypes.COLOR || getValueType(value) === ValueTypes.COLOR_OR_STRING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the provided value or expression is valid, and that the types used are compatible.
|
||||||
|
*
|
||||||
|
* Will throw an exception if found to be invalid.
|
||||||
|
*
|
||||||
|
* @param {import("../style/LiteralStyle").ExpressionValue} value Either literal or an operator.
|
||||||
|
*/
|
||||||
|
export function check(value) {
|
||||||
|
const v = value;
|
||||||
|
|
||||||
|
// these will be used to validate types in the expressions
|
||||||
|
function checkNumber(value) {
|
||||||
|
if (!isValueTypeNumber(value)) {
|
||||||
|
throw new Error(`A numeric value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function checkColor(value) {
|
||||||
|
if (!isValueTypeColor(value)) {
|
||||||
|
throw new Error(`A color value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function checkString(value) {
|
||||||
|
if (!isValueTypeString(value)) {
|
||||||
|
throw new Error(`A string value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// first check that the value is of a recognized kind
|
||||||
|
if (!isValueTypeColor(value) && !isValueTypeNumber(value) && !isValueTypeString(value)) {
|
||||||
|
throw new Error(`No type could be inferred from the following expression: ${JSON.stringify(value)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check operator arguments
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
switch (value[0]) {
|
||||||
|
case 'get':
|
||||||
|
case 'var':
|
||||||
|
checkString(v[1]);
|
||||||
|
break;
|
||||||
|
case 'time':
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
case '+':
|
||||||
|
checkNumber(v[1]);
|
||||||
|
checkNumber(v[2]);
|
||||||
|
break;
|
||||||
|
case 'clamp':
|
||||||
|
checkNumber(v[1]);
|
||||||
|
checkNumber(v[2]);
|
||||||
|
checkNumber(v[3]);
|
||||||
|
break;
|
||||||
|
case 'stretch':
|
||||||
|
checkNumber(v[1]);
|
||||||
|
checkNumber(v[2]);
|
||||||
|
checkNumber(v[3]);
|
||||||
|
checkNumber(v[4]);
|
||||||
|
checkNumber(v[5]);
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
case '>=':
|
||||||
|
case '<':
|
||||||
|
case '<=':
|
||||||
|
case '==':
|
||||||
|
checkNumber(v[1]);
|
||||||
|
checkNumber(v[2]);
|
||||||
|
break;
|
||||||
|
case '!':
|
||||||
|
checkNumber(v[1]);
|
||||||
|
break;
|
||||||
|
case 'between':
|
||||||
|
checkNumber(v[1]);
|
||||||
|
checkNumber(v[2]);
|
||||||
|
checkNumber(v[3]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'interpolate':
|
||||||
|
checkNumber(v[1]);
|
||||||
|
checkColor(v[2]);
|
||||||
|
checkColor(v[3]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: throw new Error(`Unrecognized operator in style expression: ${JSON.stringify(value)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the provided expressions and produces a GLSL-compatible assignment string, such as:
|
* Parses the provided expressions and produces a GLSL-compatible assignment string, such as:
|
||||||
* `['add', ['*', ['get', 'size'], 0.001], 12] => '(a_size * (0.001)) + (12.0)'
|
* `['add', ['*', ['get', 'size'], 0.001], 12] => '(a_size * (0.001)) + (12.0)'
|
||||||
@@ -139,6 +226,8 @@ export function isValueTypeColor(value) {
|
|||||||
* @returns {string} Assignment string.
|
* @returns {string} Assignment string.
|
||||||
*/
|
*/
|
||||||
export function parse(value, attributes, attributePrefix, variables) {
|
export function parse(value, attributes, attributePrefix, variables) {
|
||||||
|
check(value);
|
||||||
|
|
||||||
const v = value;
|
const v = value;
|
||||||
function p(value) {
|
function p(value) {
|
||||||
return parse(value, attributes, attributePrefix, variables);
|
return parse(value, attributes, attributePrefix, variables);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
check,
|
||||||
formatArray,
|
formatArray,
|
||||||
formatColor,
|
formatColor,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
@@ -248,6 +249,88 @@ void main(void) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('check', function() {
|
||||||
|
|
||||||
|
it('does not throw on valid expressions', function(done) {
|
||||||
|
check(1);
|
||||||
|
check(['get', 'myAttr']);
|
||||||
|
check(['var', 'myValue']);
|
||||||
|
check(['time']);
|
||||||
|
check(['+', ['*', ['get', 'size'], 0.001], 12]);
|
||||||
|
check(['clamp', ['get', 'attr2'], ['get', 'attr3'], 20]);
|
||||||
|
check(['stretch', ['get', 'size'], 10, 100, 4, 8]);
|
||||||
|
check(['>', 10, ['get', 'attr4']]);
|
||||||
|
check(['>=', 10, ['get', 'attr4']]);
|
||||||
|
check(['<', 10, ['get', 'attr4']]);
|
||||||
|
check(['<=', 10, ['get', 'attr4']]);
|
||||||
|
check(['==', 10, ['get', 'attr4']]);
|
||||||
|
check(['between', ['get', 'attr4'], -4.0, 5.0]);
|
||||||
|
check(['!', ['get', 'attr4']]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on unsupported types for operators', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
check(['var', 1234]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
check(['<', 0, 'aa']);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
check(['+', true, ['get', 'attr']]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws with the wrong number of arguments', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
check(['var', 1234, 456]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
check(['<', 4]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
check(['+']);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on invalid expressions', function() {
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
check(true);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
check([123, 456]);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
check(null);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
expect(thrown).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('parse', function() {
|
describe('parse', function() {
|
||||||
let attributes, prefix, variables, parseFn;
|
let attributes, prefix, variables, parseFn;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user