Expressions / added operator declarations & type checking logic
This commit is contained in:
@@ -92,41 +92,285 @@ export function getValueType(value) {
|
|||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
throw new Error(`Unhandled value type: ${JSON.stringify(value)}`);
|
throw new Error(`Unhandled value type: ${JSON.stringify(value)}`);
|
||||||
}
|
}
|
||||||
const onlyNumbers = value.every(function(v) {
|
const valueArr = /** @type {Array<*>} */(value);
|
||||||
|
const onlyNumbers = valueArr.every(function(v) {
|
||||||
return typeof v === 'number';
|
return typeof v === 'number';
|
||||||
});
|
});
|
||||||
if (onlyNumbers) {
|
if (onlyNumbers) {
|
||||||
if (value.length === 3 || value.length === 4) {
|
if (valueArr.length === 3 || valueArr.length === 4) {
|
||||||
return ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY;
|
return ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY;
|
||||||
}
|
}
|
||||||
return ValueTypes.NUMBER_ARRAY;
|
return ValueTypes.NUMBER_ARRAY;
|
||||||
}
|
}
|
||||||
if (typeof value[0] !== 'string') {
|
if (typeof valueArr[0] !== 'string') {
|
||||||
throw new Error(`Expected an expression operator but received: ${JSON.stringify(value)}`);
|
throw new Error(`Expected an expression operator but received: ${JSON.stringify(valueArr)}`);
|
||||||
}
|
}
|
||||||
switch (value[0]) {
|
const operator = Operators[valueArr[0]];
|
||||||
case 'get':
|
if (operator === undefined) {
|
||||||
case 'var':
|
throw new Error(`Unrecognized expression operator: ${JSON.stringify(valueArr)}`);
|
||||||
case 'time':
|
}
|
||||||
case '*':
|
return operator.getReturnType(valueArr.slice(1));
|
||||||
case '/':
|
}
|
||||||
case '+':
|
|
||||||
case '-':
|
function assertNumber(value) {
|
||||||
case 'clamp':
|
if (!(getValueType(value) & ValueTypes.NUMBER)) {
|
||||||
case 'stretch':
|
throw new Error(`A numeric value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
case 'mod':
|
|
||||||
case 'pow':
|
|
||||||
case '>':
|
|
||||||
case '>=':
|
|
||||||
case '<':
|
|
||||||
case '<=':
|
|
||||||
case '==':
|
|
||||||
case '!':
|
|
||||||
case 'between':
|
|
||||||
return ValueTypes.NUMBER;
|
|
||||||
case 'interpolate':
|
|
||||||
return ValueTypes.COLOR;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unrecognized expression operator: ${JSON.stringify(value)}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function assertColor(value) {
|
||||||
|
if (!(getValueType(value) & ValueTypes.COLOR)) {
|
||||||
|
throw new Error(`A color value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertString(value) {
|
||||||
|
if (!(getValueType(value) & ValueTypes.STRING)) {
|
||||||
|
throw new Error(`A string value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertBoolean(value) {
|
||||||
|
if (!(getValueType(value) & ValueTypes.BOOLEAN)) {
|
||||||
|
throw new Error(`A boolean value was expected, got ${JSON.stringify(value)} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function assertArgsCount(args, count) {
|
||||||
|
if (args.length !== count) {
|
||||||
|
throw new Error(`Exactly ${count} arguments were expected, got ${args.length} instead`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context available during the parsing of an expression.
|
||||||
|
* @typedef {Object} ParsingContext
|
||||||
|
* @property {boolean} inFragmentShader If false, means the expression output should be made for a vertex shader
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An operator declaration must contain two methods: `getReturnType` which returns a type based on
|
||||||
|
* the operator arguments, and `toGlsl` which returns a GLSL-compatible string.
|
||||||
|
* Note: both methods can process arguments recursively.
|
||||||
|
* @typedef {Object} Operator
|
||||||
|
* @property {function(...ExpressionValue): ValueTypes|number} getReturnType Returns one or several types
|
||||||
|
* @property {function(ParsingContext, ...ExpressionValue): string} toGlsl Returns a GLSL-compatible string
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator declarations
|
||||||
|
* @type {Object<string, Operator>}
|
||||||
|
*/
|
||||||
|
export const Operators = {
|
||||||
|
'get': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.ANY;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
const prefix = context.inFragmentShader ? 'v_' : 'a_';
|
||||||
|
assertArgsCount(args, 1);
|
||||||
|
assertString(args[0]);
|
||||||
|
return prefix + args[0];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'var': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.ANY;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 1);
|
||||||
|
assertString(args[0]);
|
||||||
|
return `u_${args[0]}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'time': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 0);
|
||||||
|
return 'u_time';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'*': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} * ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'/': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} / ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'+': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} + ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'-': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} - ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'clamp': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 3);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
assertNumber(args[2]);
|
||||||
|
return `clamp(${args[0]}, ${args[1]}, ${args[2]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'stretch': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 5);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
assertNumber(args[2]);
|
||||||
|
assertNumber(args[3]);
|
||||||
|
assertNumber(args[4]);
|
||||||
|
const low1 = args[1];
|
||||||
|
const high1 = args[2];
|
||||||
|
const low2 = args[3];
|
||||||
|
const high2 = args[4];
|
||||||
|
return `((clamp(${args[0]}, ${low1}, ${high1}) - ${low1}) * ((${high2} - ${low2}) / (${high1} - ${low1})) + ${low2})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'mod': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `mod(${args[0]}, ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'pow': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.NUMBER;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `pow(${args[0]}, ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'>': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} > ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'>=': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} >= ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'<': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} < ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'<=': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} <= ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'==': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 2);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
return `(${args[0]} == ${args[1]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'!': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 1);
|
||||||
|
assertBoolean(args[0]);
|
||||||
|
return `(!${args[0]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'between': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.BOOLEAN;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 3);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertNumber(args[1]);
|
||||||
|
assertNumber(args[2]);
|
||||||
|
return `(${args[0]} >= ${args[1]} && ${args[0]} <= ${args[2]})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'interpolate': {
|
||||||
|
getReturnType: function(...args) {
|
||||||
|
return ValueTypes.COLOR;
|
||||||
|
},
|
||||||
|
toGlsl: function(context, ...args) {
|
||||||
|
assertArgsCount(args, 3);
|
||||||
|
assertNumber(args[0]);
|
||||||
|
assertColor(args[1]);
|
||||||
|
assertColor(args[2]);
|
||||||
|
return `mix(${args[1]}, ${args[2]}, ${args[0]})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -36,6 +36,28 @@ describe('ol.style.expressions', function() {
|
|||||||
done(true);
|
done(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('correctly analyzes operator return types', function() {
|
||||||
|
expect(getValueType(['get', 'myAttr'])).to.eql(ValueTypes.ANY);
|
||||||
|
expect(getValueType(['var', 'myValue'])).to.eql(ValueTypes.ANY);
|
||||||
|
expect(getValueType(['time'])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['+', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['-', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['/', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['*', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['clamp', ['get', 'attr2'], ['get', 'attr3'], 20])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['stretch', ['get', 'size'], 10, 100, 4, 8])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['pow', 10, 2])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['mod', ['time'], 10])).to.eql(ValueTypes.NUMBER);
|
||||||
|
expect(getValueType(['>', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['>=', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['<', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['<=', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['==', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['between', ['get', 'attr4'], -4.0, 5.0])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['!', ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
|
||||||
|
expect(getValueType(['interpolate', ['get', 'attr4'], [255, 255, 255, 1], 'transparent'])).to.eql(ValueTypes.COLOR);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user