Skip to content

Commit

Permalink
Refactor to more object-oriented approach
Browse files Browse the repository at this point in the history
  • Loading branch information
Anand Thakker committed Jun 30, 2017
1 parent e64e953 commit 8f9f2df
Show file tree
Hide file tree
Showing 12 changed files with 922 additions and 779 deletions.
97 changes: 48 additions & 49 deletions src/style-spec/function/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,19 @@ const assert = require('assert');

module.exports = compileExpression;

const expressions = require('./expressions');
const parseExpression = require('./parse');
const {
LiteralExpression,
parseExpression,
ParsingContext,
ParsingError
} = require('./expression');
const expressions = require('./definitions');
const typecheck = require('./type_check');
const evaluationContext = require('./evaluation_context');

/*::
import type { Type } from './types.js';
import type { TypedExpression } from './type_check.js';
import type { Definition } from './expressions.js';
export type CompiledExpression = {|
result: 'success',
js: string,
type: Type,
isFeatureConstant: boolean,
isZoomConstant: boolean,
expression: TypedExpression,
function?: Function
|}
import type { Expression, CompiledExpression } from './expression.js';
type CompileError = {|
error: string,
Expand Down Expand Up @@ -63,40 +54,49 @@ const evaluationContext = require('./evaluation_context');
* @private
*/
function compileExpression(
definitions: {[string]: Definition},
expr: mixed,
expectedType?: Type
) {
const parsed = parseExpression(definitions, expr);
if (parsed.error) {
return {
result: 'error',
errors: [parsed]
};
let parsed;
try {
parsed = parseExpression(expr, new ParsingContext(expressions));
} catch (e) {
if (e instanceof ParsingError) {
return {
result: 'error',
errors: [{key: e.key, error: e.message}]
};
}
throw e;
}

if (parsed.type) {
const typecheckResult = typecheck(expectedType || parsed.type, parsed);
if (typecheckResult.errors) {
return { result: 'error', errors: typecheckResult.errors };
const checked = typecheck(expectedType || parsed.type, parsed);
if (checked.result === 'error') {
return checked;
}

const compiled = compile(null, typecheckResult);
const compiled = compile(null, checked.expression);
if (compiled.result === 'success') {
const fn = new Function('mapProperties', 'feature', `
mapProperties = mapProperties || {};
if (feature && typeof feature === 'object') {
feature = this.object(feature);
}
var props;
if (feature && feature.type === 'Object') {
props = (typeof feature.value.properties === 'object') ?
this.object(feature.value.properties) : feature.value.properties;
}
if (!props) { props = this.object({}); }
return this.unwrap(${compiled.js})
`);
compiled.function = fn.bind(evaluationContext());
try {
const fn = new Function('mapProperties', 'feature', `
mapProperties = mapProperties || {};
if (feature && typeof feature === 'object') {
feature = this.object(feature);
}
var props;
if (feature && feature.type === 'Object') {
props = (typeof feature.value.properties === 'object') ?
this.object(feature.value.properties) : feature.value.properties;
}
if (!props) { props = this.object({}); }
return this.unwrap(${compiled.js})
`);
compiled.function = fn.bind(evaluationContext());
} catch (e) {
console.log(compiled.js);
throw e;
}
}

return compiled;
Expand All @@ -105,11 +105,11 @@ function compileExpression(
assert(false, 'parseExpression should always return either error or typed expression');
}

function compile(expected: Type | null, e: TypedExpression) /*: CompiledExpression | CompileErrors */ {
if (e.literal) {
function compile(expected: Type | null, e: Expression) /*: CompiledExpression | CompileErrors */ {
if (e instanceof LiteralExpression) {
return {
result: 'success',
js: JSON.stringify(e.value),
js: e.compile().js,
type: e.type,
isFeatureConstant: true,
isZoomConstant: true,
Expand All @@ -119,8 +119,8 @@ function compile(expected: Type | null, e: TypedExpression) /*: CompiledExpressi
const errors: Array<CompileError> = [];
const compiledArgs: Array<CompiledExpression> = [];

for (let i = 0; i < e.arguments.length; i++) {
const arg = e.arguments[i];
for (let i = 0; i < e.args.length; i++) {
const arg = e.args[i];
const param = e.type.params[i];
const compiledArg = compile(param, arg);
if (compiledArg.result === 'error') {
Expand All @@ -137,8 +137,7 @@ function compile(expected: Type | null, e: TypedExpression) /*: CompiledExpressi
let isFeatureConstant = compiledArgs.reduce((memo, arg) => memo && arg.isFeatureConstant, true);
let isZoomConstant = compiledArgs.reduce((memo, arg) => memo && arg.isZoomConstant, true);

const definition = expressions[e.name];
const compiled = definition.compile(compiledArgs, e);
const compiled = e.compile(compiledArgs);
if (compiled.errors) {
return {
result: 'error',
Expand Down
130 changes: 130 additions & 0 deletions src/style-spec/function/definitions/curve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
'use strict';

// @flow

const {
NullType,
NumberType,
ColorType,
typename,
lambda,
nargs
} = require('../types');

const { ParsingError, LiteralExpression, LambdaExpression } = require('../expression');

/*::
import type { Expression, CompiledExpression } from '../expression';
import type { LambdaType } from '../types';
type InterpolationType = { name: 'step' } | { name: 'linear' } | { name: 'exponential', base: number }
*/

class CurveExpression extends LambdaExpression {
interpolation: InterpolationType;
constructor(key: *, type: *, args: *, interpolation: InterpolationType) {
super(key, type, args);
this.interpolation = interpolation;
}

static getName() { return 'curve'; }
static getType() { return lambda(typename('T'), NullType, NumberType, nargs(Infinity, NumberType, typename('T'))); }

static parse(args, context) {
// pull out the interpolation type argument for specialized parsing,
// and replace it with `null` so that other arguments' "key"s stay the
// same for error reporting.
const interp = args[0];
const fixedArgs = [null].concat(args.slice(1));
const expression: CurveExpression = (super.parse(fixedArgs, context): any);

if (!Array.isArray(interp) || interp.length === 0)
throw new ParsingError(`${context.key}.1`, `Expected an interpolation type expression, but found ${String(interp)} instead.`);

if (interp[0] === 'step') {
expression.interpolation = { name: 'step' };
} else if (interp[0] === 'linear') {
expression.interpolation = { name: 'linear' };
} else if (interp[0] === 'exponential') {
const base = interp[1];
if (typeof base !== 'number')
throw new ParsingError(`${context.key}.1.1`, `Exponential interpolation requires a numeric base.`);
expression.interpolation = {
name: 'exponential',
base
};
} else throw new ParsingError(`${context.key}.1.0`, `Unknown interpolation type ${String(interp[0])}`);
return expression;
}

serialize(withTypes: boolean) {
const type = this.type.result.name;
const args = this.args.map(e => e.serialize(withTypes));
const interp = [this.interpolation.name];
if (this.interpolation.name === 'exponential') {
interp.push(this.interpolation.base);
}
args.splice(0, 1, interp);
return [ `curve${(withTypes ? `: ${type}` : '')}` ].concat(args);
}

applyType(type: LambdaType, args: Array<Expression>): Expression {
return new this.constructor(this.key, type, args, this.interpolation);
}

compile(args: Array<CompiledExpression>) {
if (args.length < 4) return {
errors: [`Expected at least four arguments, but found only ${args.length}.`]
};

const firstOutput = args[3];
let resultType;
if (firstOutput.type === NumberType) {
resultType = 'number';
} else if (firstOutput.type === ColorType) {
resultType = 'color';
} else if (
firstOutput.type.kind === 'array' &&
firstOutput.type.itemType === NumberType
) {
resultType = 'array';
} else if (this.interpolation.name !== 'step') {
return {
errors: [`Type ${firstOutput.type.name} is not interpolatable, and thus cannot be used as a ${this.interpolation.name} curve's output type.`]
};
}

const stops = [];
const outputs = [];
for (let i = 2; (i + 1) < args.length; i += 2) {
const input = args[i].expression;
const output = args[i + 1];
if (
!(input instanceof LiteralExpression) ||
typeof input.value !== 'number'
) {
return {
errors: [ 'Input/output pairs for "curve" expressions must be defined using literal numeric values (not computed expressions) for the input values.' ]
};
}

if (stops.length && stops[stops.length - 1] > input.value) {
return {
errors: [ 'Input/output pairs for "curve" expressions must be arranged with input values in strictly ascending order.' ]
};
}

stops.push(input.value);
outputs.push(output.js);
}

return {js: `
(function () {
var input = ${args[1].js};
var stopInputs = [${stops.join(', ')}];
var stopOutputs = [${outputs.map(o => `() => ${o}`).join(', ')}];
return this.evaluateCurve(input, stopInputs, stopOutputs, ${JSON.stringify(this.interpolation)}, ${JSON.stringify(resultType)});
}.bind(this))()`};
}
}

module.exports = CurveExpression;
Loading

0 comments on commit 8f9f2df

Please sign in to comment.