Skip to content

Commit

Permalink
Implement match
Browse files Browse the repository at this point in the history
  • Loading branch information
Anand Thakker committed Jun 20, 2017
1 parent fa09144 commit 2d49f8e
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/style-spec/function/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ const evaluationContext = require('./evaluation_context');
|}
type CompileError = {
type CompileError = {|
error: string,
key: string
}
|}
type CompileErrors = {|
result: 'error',
Expand Down
35 changes: 35 additions & 0 deletions src/style-spec/function/expressions.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,41 @@ const expressions: { [string]: Definition } = {
return { js: result.join(':') };
}
},
'match': {
name: 'match',
// note that, since they're pulled out during parsing, the input
// values of type T aren't reflected in the signature here
type: lambda(typename('T'), typename('U'), nargs(typename('T'))),
compile: (e, args) => {
if (!e.matchInputs) { throw new Error('Missing match input values'); }
const inputs = e.matchInputs;
if (args.length !== inputs.length + 2) {
return {
errors: [`Expected ${2 * inputs.length + 2} arguments, but found ${inputs.length + args.length} instead.`]
};
}

const input = args[0].js;
const outputs = args.slice(1).map(a => `() => ${a.js}`);
const inputMap = {};
for (let i = 0; i < inputs.length; i++) {
for (const v of inputs[i]) {
inputMap[String(v.value)] = i;
}
}

return {js: `
(function () {
var outputs = [${outputs.join(', ')}];
var inputMap = ${JSON.stringify(inputMap)};
var input = ${input};
var outputIndex = inputMap[${input}];
return typeof outputIndex === 'number' ? outputs[outputIndex]() :
outputs[${outputs.length - 1}]();
}.bind(this))()`};
}
},

'curve': {
name: 'curve',
type: lambda(typename('T'), InterpolationType, NumberType, nargs(NumberType, typename('T'))),
Expand Down
62 changes: 59 additions & 3 deletions src/style-spec/function/parse.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';

// @flow

const {
Expand Down Expand Up @@ -83,12 +84,63 @@ function parseExpression(expr: mixed, path: Array<number> = []) /*: TypedExpress
};
}

// special case parsing for `match`
if (op === 'match') {
if (expr.length < 3) return {
key,
error: `Expected at least 2 arguments, but found only ${expr.length - 1}.`
};

const inputExpression = parseExpression(expr[1], path.concat(1));
if (inputExpression.error) return inputExpression;

// parse input/output pairs
const matchInputs = [];
const outputExpressions = [];
for (let i = 2; i < expr.length - 2; i += 2) {
const inputValues = Array.isArray(expr[i]) ? expr[i] : [expr[i]];
if (inputValues.length === 0) {
return {
key: `${key}.${i}`,
error: 'Expected at least one input value.'
};
}

const parsedInputs = [];
for (let j = 0; j < inputValues.length; j++) {
const parsedValue = parseExpression(inputValues[j], path.concat(i, j));
if (parsedValue.error) return parsedValue;
if (!parsedValue.literal) return {
key: `${key}.${i}.${j}`,
error: 'Match inputs must be literal primitive values or arrays of literal primitive values.'
};
parsedInputs.push(parsedValue);
}
matchInputs.push(parsedInputs);

const output = parseExpression(expr[i + 1], path.concat(i));
if (output.error) return output;
outputExpressions.push(output);
}

const otherwise = parseExpression(expr[expr.length - 1], path.concat(expr.length - 1));
if (otherwise.error) return otherwise;
outputExpressions.push(otherwise);

return {
literal: false,
name: 'match',
type: definition.type,
matchInputs,
arguments: [inputExpression].concat(outputExpressions),
key
};
}

const args = [];
for (const arg of expr.slice(1)) {
const parsedArg = parseExpression(arg, path.concat(1 + args.length));
if (parsedArg.error) {
return parsedArg;
}
if (parsedArg.error) return parsedArg;
args.push(parsedArg);
}

Expand All @@ -101,3 +153,7 @@ function parseExpression(expr: mixed, path: Array<number> = []) /*: TypedExpress
};
}

function isPrimitive(x) {
const type = typeof x;
return type === 'number' || type === 'string' || type === 'boolean';
}
29 changes: 24 additions & 5 deletions src/style-spec/function/type_check.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@
import type { ExpressionName } from './expression_name.js';
export type TypeError = {
export type TypeError = {|
error: string,
key: string
}
|}
export type TypedLambdaExpression = {|
literal: false,
name: ExpressionName,
type: LambdaType,
arguments: Array<TypedExpression>,
key: string
key: string,
matchInputs?: Array<Array<TypedLiteralExpression>>
|}
export type TypedLiteralExpression = {|
Expand Down Expand Up @@ -114,17 +115,35 @@ function typeCheckExpression(expected: Type, e: TypedExpression) /*: TypedExpres
}
}

let matchInputs;
if (e.matchInputs) {
matchInputs = [];
const inputType = resolvedArgTypes[0];
for (const inputGroup of e.matchInputs) {
const checkedGroup = [];
for (const inputValue of inputGroup) {
const result = typeCheckExpression(inputType, inputValue);
if (result.errors) {
errors.push.apply(errors, result.errors);
} else {
checkedGroup.push(result);
}
}
matchInputs.push(checkedGroup);
}
}

if (errors.length > 0) return { errors };

return {
literal: false,
name: e.name,
type: lambda(expectedResult, ...resolvedArgTypes),
arguments: checkedArgs,
key: e.key
key: e.key,
matchInputs
};
}

}

// returns null if the type matches, or an error message if not
Expand Down
29 changes: 29 additions & 0 deletions test/integration/expression-tests/match/basic/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"expression": [
"string", [
"match",
["string", ["get", ["properties"], "x"]],
"a", "Apple",
"b", "Banana",
"Kumquat"
]
],
"evaluate": [
[{}, {"properties": {"x": "a"}}],
[{}, {"properties": {"x": "b"}}],
[{}, {"properties": {"x": "c"}}]
],
"expected": {
"compileResult": {
"result": "success",
"isFeatureConstant": false,
"isZoomConstant": true,
"type": "string"
},
"evaluateResults": [
"Apple",
"Banana",
"Kumquat"
]
}
}

0 comments on commit 2d49f8e

Please sign in to comment.