UNPKG

wael-lib

Version:

Well-Known Text Arithmetic Expression Language

1 lines 87.5 kB
{"version":3,"sources":["../../src/main.ts","../../src/interpreter/interpreter.ts","../../src/interpreter/types.ts","../../src/interpreter/helpers.ts","../../src/interpreter/scope.ts","../../src/interpreter/built-in-functions.ts","../../src/interpreter/grammar.ts","../../src/lib.ts"],"sourcesContent":["import * as turf from '@turf/turf';\nimport * as wellknown from 'wellknown';\n\nimport { Interpreter } from \"./interpreter/interpreter\";\nimport { STD_LIB } from './lib';\nimport { Scope } from \"./interpreter/scope\";\n\nexport enum OutputFormat {\n WKT = 'WKT',\n GeoJSON = 'GeoJSON'\n}\n\nexport interface Options {\n outputFormat?: OutputFormat,\n outputNonGeoJSON?: boolean;\n scope?: Scope;\n storeHistoricalEvaluations?: boolean;\n}\n\nexport const DEFAULT_OPTIONS: Options = {\n outputFormat: OutputFormat.WKT,\n storeHistoricalEvaluations: true\n}\n\nexport class Wael {\n private options: Options;\n private evaluationCount = 0;\n static IDENTIFIER_LAST = '$?';\n\n constructor(\n initialOptions?: Options\n ) {\n this.options = {\n ...DEFAULT_OPTIONS,\n ...initialOptions,\n scope: initialOptions?.scope ?? Interpreter.createGlobalScope(),\n };\n \n // Import standard library\n Interpreter.evaluateInput(`StdLib = ${STD_LIB}; Import(StdLib()) Using (*)`, this.options.scope)\n }\n\n getEvaluationCount(): number {\n return this.evaluationCount;\n }\n\n evaluate(input: string, overrideOptions?: Partial<Options>) {\n const options = {\n ...this.options,\n ...overrideOptions\n };\n\n const result = Interpreter.evaluateInput(input, options.scope);\n\n // Track evaluations\n options.scope?.store(Wael.IDENTIFIER_LAST, result);\n if (options.storeHistoricalEvaluations) {\n const indexedResultLabel = `$${this.evaluationCount}`;\n options.scope?.store(indexedResultLabel, result);\n }\n this.evaluationCount++;\n\n if (typeof result === 'object' && typeof result?.type === 'string') {\n switch(options?.outputFormat) {\n case OutputFormat.WKT:\n return wellknown.stringify(result);\n case OutputFormat.GeoJSON:\n return turf.feature(result) as any;\n default:\n break;\n }\n }\n \n const output = Wael.getOutputString(result, options.outputFormat);\n if (options.outputNonGeoJSON) {\n if (options.outputFormat === OutputFormat.GeoJSON) {\n return result ?? undefined;\n }\n return output;\n } else {\n const outputString = typeof output === 'string' ? output : JSON.stringify(output, undefined, 2)\n throw new Error(`Invalid '${options.outputFormat}' evaluation result: ${outputString}`)\n }\n }\n\n static evaluate(input: string, options?: Partial<Options>) {\n return new Wael(options).evaluate(input);\n }\n\n private static getOutputString(result: any, outputFormat?: OutputFormat) {\n if (typeof result === 'object') {\n if (outputFormat === OutputFormat.WKT) {\n const properties = Object.keys(result).map(key => {\n return ` ${key} = ${result[key]?.toString()}`\n });\nreturn `(\n${properties.join(';\\n')}\n)`\n } \n }\n return `${result}`\n }\n}\n\n","// import ohm from 'ohm-js';\nimport * as ohm from 'ohm-js';\nimport * as turf from '@turf/turf'\n\nimport { GeometryType, UNIT } from './types';\nimport {\n OperationNotSupported,\n arithmeticOperationExp,\n convertToGeometry,\n geometryAccessor,\n getArrayLikeItems,\n getGeometryType,\n isAnyGeometryType,\n isGeometryType,\n isNumber,\n toString,\n transform\n} from './helpers';\nimport { Scope, ScopeBindings } from './scope';\n\nimport { BuiltInFunctions } from './built-in-functions';\nimport { GRAMMAR } from './grammar';\nimport { readFileSync } from 'fs';\nimport syncFetch from 'sync-fetch'\n\nconst grammarString = GRAMMAR;\n\nexport namespace Interpreter {\n\n export const IMPORT_DEFAULT_IDENTIFIER = 'Default';\n export const IMPORT_USING_ALL = '*';\n \n export const STANDARD_LIBRARY: ScopeBindings = {};\n const math: { [prop: string]: any } = {};\n Object.getOwnPropertyNames(Math).forEach(prop => {\n math[prop] = (Math as any)[prop];\n });\n STANDARD_LIBRARY['Math'] = math;\n STANDARD_LIBRARY['Flatten'] = BuiltInFunctions.Flatten;\n STANDARD_LIBRARY['PointCircle'] = BuiltInFunctions.PointCircle;\n STANDARD_LIBRARY['PointGrid'] = BuiltInFunctions.PointGrid;\n STANDARD_LIBRARY['ToLineString'] = BuiltInFunctions.ToLineString;\n STANDARD_LIBRARY['ToMultiPoint'] = BuiltInFunctions.ToMultiPoint;\n STANDARD_LIBRARY['ToPolygon'] = BuiltInFunctions.ToPolygon;\n STANDARD_LIBRARY['ToGeometryCollection'] = BuiltInFunctions.ToGeometryCollection;\n STANDARD_LIBRARY['_Rotate'] = BuiltInFunctions.Rotate;\n STANDARD_LIBRARY['_Round'] = BuiltInFunctions.Round;\n\n export const createGlobalScope = () => new Scope(undefined, undefined, STANDARD_LIBRARY);\n \n export function evaluateInput(input: string, initialScope?: Scope): any {\n const GLOBAL_SCOPE = createGlobalScope();\n let currentScope = initialScope || GLOBAL_SCOPE;\n const grammar = ohm.grammar(grammarString);\n const semantics = grammar.createSemantics();\n semantics.addOperation('eval', {\n stringLiteral(_leftQuote, str, _rightQuote) {\n return str.sourceString;\n },\n ImportAllExpression(exp) {\n const ret = exp.eval();\n if (ret) {\n // Return data import\n return ret;\n }\n\n // Return function import object\n const importObj = currentScope.useImports();\n return importObj;\n },\n ImportUsingExpression(importAllExp, _keyword, identifierList) {\n importAllExp.eval();\n const identifiers = identifierList.eval();\n let importObj;\n if (identifiers === IMPORT_USING_ALL) {\n importObj = currentScope.useNamedImports(Object.keys(currentScope.availableBindings));\n } else {\n importObj = currentScope.useNamedImports(identifiers);\n }\n return importObj;\n },\n ImportUsingAllParams(_) {\n return IMPORT_USING_ALL;\n },\n ImportExternalExp(_keyword, importUri) {\n const uri:string = importUri.eval();\n\n let data: string;\n if (uri.startsWith('http://') || uri.startsWith('https://')) {\n data = syncFetch(uri).text();\n } else {\n data = readFileSync(uri, 'utf8');\n }\n currentScope = currentScope.push();\n let ret = undefined;\n let bindings: ScopeBindings | undefined = undefined;\n try {\n ret = convertToGeometry(JSON.parse(data));\n } catch {\n try {\n const libRet = evaluateInput(data, currentScope);\n bindings = {};\n bindings[IMPORT_DEFAULT_IDENTIFIER] = libRet;\n } catch {\n throw new Error(`Unable to import file: ${uri}`);\n }\n }\n currentScope = currentScope.pop(bindings) || GLOBAL_SCOPE;\n return ret;\n },\n ImportFunctionExp(_keyword, importFn) {\n importFn.eval();\n },\n IfThenElseExp(_if, c, _then, exp1, _else, exp2) {\n const condition = c.eval();\n if (condition) {\n return exp1.eval();\n }\n return exp2.eval();\n },\n booleanValue(val) {\n const value = val.sourceString.toLocaleLowerCase();\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n throw new Error(`Invalid boolean value: ${val.sourceString}`);\n },\n EqualityExp(v1, op, v2) {\n const value1 = v1.eval();\n const operator = op.sourceString;\n const value2 = v2.eval();\n switch (operator.trim().toLocaleLowerCase()) {\n case \"==\":\n return value1 === value2;\n case \"!=\":\n return value1 !== value2;\n case \"and\":\n return value1 && value2;\n case \"or\":\n return value1 || value2;\n }\n throw new Error(`Operator not supported: ${operator}`);\n },\n NotExp(_, exp) {\n return !exp.eval();\n },\n geometryKeyword(keyword) {\n return keyword.sourceString;\n },\n CompareExp(v1, op, v2) {\n const value1 = v1.eval();\n if (!isNumber(value1)) {\n throw new Error(`Expected a number for ${v1.sourceString} but got: ${toString(value1)}`)\n }\n const operator = op.sourceString;\n const value2 = v2.eval();\n if (!isNumber(value2)) {\n throw new Error(`Expected a number for ${v2.sourceString} but got: ${toString(value2)}`)\n }\n switch (operator.trim()) {\n case \"<\":\n return value1 < value2;\n case \"<=\":\n return value1 <= value2;\n case \">\":\n return value1 > value2;\n case \">=\":\n return value1 >= value2;\n }\n throw new Error(`Operator not supported: ${operator}`);\n },\n ConcatExp(g1, _op, g2) {\n const geom1 = g1.eval();\n const geom2 = g2.eval();\n\n const type1 = getGeometryType(geom1);\n const type2 = getGeometryType(geom2);\n if (!type1 || !type2) {\n throw new Error(`Expected geometry types for concatenation but found geom1: ${toString(geom1)} and geom2: ${toString(geom2)}`);\n }\n \n if (type1 === type2) {\n\n const list1: any[] = getArrayLikeItems(geom1);\n const list2: any[] = getArrayLikeItems(geom2);\n \n // Point collections\n if (\n type1 === GeometryType.LineString || \n type1 === GeometryType.MultiPoint || \n type1 === GeometryType.GeometryCollection \n ) {\n const combined = list1.concat(list2);\n if (type1 === GeometryType.LineString) {\n return turf.lineString(combined).geometry;\n }\n if (type1 === GeometryType.MultiPoint) {\n return turf.multiPoint(combined).geometry;\n }\n if (type1 === GeometryType.GeometryCollection) {\n return turf.geometryCollection(combined).geometry;\n }\n }\n }\n \n if (\n type1 === GeometryType.LineString || \n type1 === GeometryType.MultiPoint \n ) {\n const list1: any[] = getArrayLikeItems(geom1);\n let list2: any;\n if (\n type2 === GeometryType.LineString || \n type2 === GeometryType.MultiPoint \n ) {\n list2 = getArrayLikeItems(geom2);\n } else {\n if (type2 === GeometryType.Point) {\n list2 = [geom2.coordinates];\n }\n }\n if (list2) {\n const combined = list1.concat(list2);\n if (type1 === GeometryType.LineString) {\n return turf.lineString(combined).geometry;\n }\n if (type1 === GeometryType.MultiPoint) {\n return turf.multiPoint(combined).geometry;\n }\n if (type1 === GeometryType.GeometryCollection) {\n return turf.geometryCollection(combined).geometry;\n }\n }\n }\n\n if (type1 === GeometryType.GeometryCollection) {\n return turf.geometryCollection(geom1.geometries.concat([geom2])).geometry;\n }\n\n if (type2 === GeometryType.GeometryCollection) {\n return turf.geometryCollection([geom1].concat(...geom2.geometries)).geometry;\n }\n\n return turf.geometryCollection([geom1, geom2]).geometry;\n },\n PipeExp(a, op, f) {\n const operator = op.sourceString;\n const val = a.eval();\n const fn = f.eval();\n\n if (operator === '|') {\n return fn(val);\n }\n\n if (operator === '|*') {\n return transform(val, fn);\n }\n\n const list: any[] = getArrayLikeItems(val);\n let listFn: any;\n switch (operator) {\n case '||':\n listFn = Array.prototype.map;\n break;\n case '|~':\n listFn = Array.prototype.filter;\n break;\n case '|>':\n listFn = Array.prototype.reduce;\n break;\n default:\n throw new Error(`Operator ${operator} not supported`)\n }\n\n if (isGeometryType(GeometryType.GeometryCollection, val)) {\n const mappedList = listFn.call(list, fn);\n if (isAnyGeometryType(mappedList)) {\n return mappedList;\n }\n return turf.geometryCollection(mappedList).geometry;\n }\n\n if (isGeometryType(GeometryType.LineString, val)) {\n let mappedList = listFn.call(\n list.map(coords => turf.point(coords).geometry),\n fn\n );\n if (isAnyGeometryType(mappedList)) {\n return mappedList;\n }\n mappedList = mappedList.map((v: any) => v.coordinates);\n return turf.lineString(mappedList).geometry;\n }\n\n if (isGeometryType(GeometryType.MultiPoint, val)) {\n let mappedList = listFn.call(\n list.map(coords => turf.point(coords).geometry),\n fn\n );\n if (isAnyGeometryType(mappedList)) {\n return mappedList;\n }\n mappedList = mappedList.map((v: any) => v.coordinates);\n return turf.multiPoint(mappedList).geometry;\n }\n\n // TODO -- Multi line types (Polygon, MultiLineString)\n\n throw Error(`Error mapping values to geometries`);\n },\n TopLevel(scopedExpressions, _end) {\n let lastValue = scopedExpressions.eval();\n return lastValue;\n },\n GenerateExp(_keyword, numExp, valueExp) {\n const num = numExp.eval();\n if (!Number.isInteger(num) && typeof num !== 'function') {\n throw new Error(`Expected integer but got: ${toString(num)}`);\n }\n let value = valueExp.eval();\n let mapFn;\n if (typeof value === 'function') {\n mapFn = value;\n } else if (isAnyGeometryType(value)) {\n mapFn = () => value;\n } else {\n throw new Error(`Expected geometry type or function but got: ${toString(value)}`);\n }\n const items: any[] = [];\n if (typeof num === 'function') { \n let i = 0;\n let condition = num(i);\n while(condition) {\n const result = mapFn(i);\n if (!isAnyGeometryType(result)) {\n throw new Error(`Expected geometry type return value but got: ${toString(result)}`);\n }\n items.push(result);\n condition = num(++i);\n }\n } else {\n for (let i=0; i<num; i++) {\n const result = mapFn(i);\n if (!isAnyGeometryType(result)) {\n throw new Error(`Expected geometry type return value but got: ${toString(result)}`);\n }\n items.push(result);\n }\n }\n return turf.geometryCollection(items).geometry;\n },\n FunctionCallExp(callable, p) {\n const fn = callable.eval();\n const params = p.eval();\n if (!fn) {\n throw new Error(`${callable.sourceString} is: ${fn}`);\n }\n const value = fn(...params);\n return value;\n },\n AccessibleExp_method(val, _accessOp, prop, optionalParams: ohm.Node) {\n const value = val.eval();\n const identifier = prop.sourceString;\n const isInvocation = optionalParams.children.length > 0;\n const parameters = isInvocation ? optionalParams.children.map(c => c.eval())[0] : [];\n\n // Function type accessor\n if (typeof value === 'function') {\n if (identifier === 'bind') {\n return value.bind(undefined, ...parameters)\n }\n throw new OperationNotSupported(`Method ${identifier} not supported on function type`);\n }\n \n // Geometry type accessor\n if (isAnyGeometryType(value)) {\n return geometryAccessor(val, prop, parameters);\n }\n\n // Object type accessor\n const resolvedValue = value[identifier];\n if (typeof resolvedValue === 'function' && isInvocation) {\n return resolvedValue(...parameters);\n }\n return resolvedValue;\n },\n Invocation(_leftParen, list, _rightParen) {\n return list.asIteration().children.map(c => c.eval());\n },\n FunctionExp(p, _, body) {\n let params = p.eval();\n params = Array.isArray(params) ? params : [params];\n let fnScope = currentScope;\n const fn = function(...values: any[]) {\n // Create new scope per function call.\n currentScope = currentScope.push({...fnScope.bindings});\n params.forEach((paramName: any, index: number) => {\n currentScope.store(paramName, values[index], undefined, false)\n });\n const ret = body.eval();\n const defaultBindings: ScopeBindings = {};\n defaultBindings[IMPORT_DEFAULT_IDENTIFIER] = ret;\n currentScope = currentScope.pop(defaultBindings) || GLOBAL_SCOPE;\n return ret;\n };\n const boundFn = fn.bind(currentScope);\n boundFn.toString = function() { return `Function(${p.sourceString} => ${body.sourceString})` }\n return boundFn;\n },\n FunctionParameters_multipleParams(_leftParen, identifierList, _rightParen) {\n const params = identifierList.asIteration().children.map(c => c.sourceString);\n return params;\n },\n FunctionParameters_single(identifier) {\n return [identifier.sourceString];\n },\n FunctionTextExp_keyword(_keyword, _leftParen, exp, _rightParen) {\n return exp.eval();\n },\n id(first, rest) {\n const key = first.sourceString + rest.sourceString;\n return currentScope.resolve(key);\n },\n AssignmentExp(exportKeyword, letKeyword, identifier, _operator, value) {\n const isPublic = !!(exportKeyword?.children?.length);\n const isLet = !!(letKeyword?.children?.length);\n const variableName = identifier.sourceString;\n const variableValue = value.eval();\n currentScope.store(variableName, variableValue, { public: isPublic }, !isLet);\n return variableValue;\n\n },\n ScopedExpressions_list(list) {\n const expressions = list.asIteration().children.map(c => c.eval());\n return expressions[expressions.length - 1];\n },\n GeneralExpression_rightComment(exp, _) {\n return exp.eval();\n },\n GeneralExpression_leftComment(_, exp) {\n return exp.eval();\n },\n Paren(_leftParen, exp, _rightParen) {\n return exp.eval();\n },\n OptionallyParen_paren(_leftParen, exp, _rightParen) {\n return exp.eval();\n },\n OptionallyBraced_brace(_leftBrace, exp, _rightBrace) {\n return exp.eval();\n },\n GeometryTaggedText(_keyword, exp) {\n return exp.eval();\n },\n GeometryCollectionText_present(_leftParen, list, _rightParen) {\n const geometries = ((list || []) as any).asIteration().children.map((c: any) => c.eval());\n return turf.geometryCollection(geometries).geometry;\n },\n MultiPolygonText_present(_leftParen, list, _rightParen) {\n const polygons = list.asIteration().children.map(c => c.eval());\n return turf.multiPolygon(polygons.map(p => p.coordinates)).geometry;\n },\n MultiLineStringText_present(_leftParen, list, _rightParen) {\n const lineStrings = list.asIteration().children.map(c => c.eval());\n return turf.multiLineString(lineStrings.map(p => p.coordinates)).geometry;\n },\n MultiPointText_present(_leftParen, list, _rightParen) {\n const points = list.eval();\n return turf.multiPoint(points.map((p: any) => p.coordinates)).geometry;\n },\n PolygonText_present(_leftParen, list, _rightParen) {\n const points = list.asIteration().children.map(c => c.eval());\n return turf.polygon(points.map(p => p.coordinates)).geometry;\n },\n LineStringText_present(_leftParen, list, _rightParen) {\n const points = list.eval();\n return turf.lineString(points.map((p: any) => p.coordinates)).geometry;\n },\n PointList(list) {\n return list.asIteration().children.map(c => c.eval());\n },\n PointTaggedText(_, point) {\n return point.eval();\n },\n PointText_present(_leftParen, point, _rightParen) {\n return point.eval();\n },\n Point(x, y) {\n return turf.point([x.eval(), y.eval()]).geometry;\n },\n ArithmeticAdd_plus(a, _, b) {\n const result = arithmeticOperationExp(a, b, (a, b) => a + b);\n if (result !== undefined) {\n return result;\n }\n throw new OperationNotSupported(`${toString(a.eval())} + ${toString(b.eval())}`);\n },\n ArithmeticAdd_minus(a, _, b) {\n const result = arithmeticOperationExp(a, b, (a, b) => a - b);\n if (result !== undefined) {\n return result;\n }\n throw new OperationNotSupported(`${toString(a.eval())} - ${toString(b.eval())}`);\n },\n ArithmeticMul_times(a, _, b) {\n const result = arithmeticOperationExp(a, b, (a, b) => a * b);\n if (result !== undefined) {\n return result;\n }\n throw new OperationNotSupported(`${toString(a.eval())} * ${toString(b.eval())}`);\n },\n ArithmeticMul_divide(a, _, b) {\n const result = arithmeticOperationExp(a, b, (a, b) => a / b);\n if (result !== undefined) {\n return result;\n }\n throw new OperationNotSupported(`${toString(a.eval())} / ${toString(b.eval())}`);\n },\n ArithmeticMul_mod(a, _, b) {\n const result = arithmeticOperationExp(a, b, (a, b) => a % b);\n if (result !== undefined) {\n return result;\n }\n throw new OperationNotSupported(`${toString(a.eval())} % ${toString(b.eval())}`);\n },\n ArithmeticExp_power(a, _, b) {\n const result = arithmeticOperationExp(a, b, (a, b) => Math.pow(a, b));\n if (result !== undefined) {\n return result;\n }\n throw new OperationNotSupported(`${toString(a.eval())} ^ ${toString(b.eval())}`);\n },\n ArithmeticPri_paren(_l, exp, _r) {\n return exp.eval();\n },\n exactNumericLiteral_decimalWithWholeNumber(whole, _, decimal) {\n return parseFloat(`${whole.sourceString}.${decimal.sourceString}`);\n },\n exactNumericLiteral_decimalWithoutWholeNumber(_, decimal) {\n return parseFloat(`.${decimal.sourceString}`);\n },\n exactNumericLiteral_wholeNumber(whole) {\n return parseInt(`${whole.sourceString}`);\n },\n approximateNumericLiteral(mantissa, e, exponent) {\n return parseFloat(`${mantissa.eval()}${e}${exponent.eval()}`);\n },\n signedNumericLiteral_signPresent(sign, numericLiteral) {\n return parseFloat(`${sign.sourceString}${numericLiteral.eval()}`);\n },\n signedNumericLiteral_signMissing(numericLiteral) {\n return numericLiteral.eval();\n },\n comment(_a, _b, _c, _d, _e) {\n return UNIT;\n },\n emptySet(_val) {\n return UNIT;\n }\n });\n \n const matchResult = grammar.match(input);\n if (matchResult.message) {\n console.error(`Message: ${matchResult.message}`)\n }\n const sem = semantics(matchResult);\n const result = sem.eval();\n return result;\n }\n \n}","export type GeoJSON = any; // TODO -- use GeoJSON type\n\nexport enum GeometryType {\n Point = 'Point',\n LineString = 'LineString',\n Polygon = 'Polygon',\n MultiPoint = 'MultiPoint',\n MultiLineString = 'MultiLineString',\n MultiPolygon = 'MultiPolygon',\n GeometryCollection = 'GeometryCollection'\n}\n\nexport type Unit = null;\nexport const UNIT: Unit = null;\n\n","import * as turf from '@turf/turf';\n\nimport { GeometryType } from \"./types\";\nimport { Point } from 'geojson';\n\nexport const isAnyGeometryType = (value: any) => {\n const types = Object.values(GeometryType);\n for (const type of types) {\n if (isGeometryType(type, value)) {\n return true;\n }\n }\n return false;\n}\n\nexport const isGeometryType = (type: GeometryType, ...values: any) => {\n for (const value of values) {\n const isType = typeof value === 'object' &&\n value?.type === type ||\n value?.geometry?.type === type;\n if (!isType) {\n return false;\n }\n }\n return true;\n};\n\nexport const getGeometryType = (value: any, isForDisplay = false): GeometryType | undefined => {\n if(typeof value === 'object') {\n const type = value?.geometry?.type || value?.type\n if (isForDisplay && type === GeometryType.GeometryCollection) {\n return GeometryType.GeometryCollection;\n }\n return type;\n }\n return undefined;\n}\n\n\nexport const isAGeometryType = (value: any, ...types: GeometryType[]) => {\n for (const type of types) {\n if(isGeometryType(type, value)) {\n return true;\n }\n }\n return false;\n};\n\nexport const getArrayLikeItems = (value: any) => {\n if (\n isGeometryType(GeometryType.LineString, value) ||\n isGeometryType(GeometryType.MultiPoint, value)\n ) {\n return value.coordinates;\n } else if (isGeometryType(GeometryType.GeometryCollection, value)) {\n return value.geometries;\n }\n return undefined;\n};\n\nexport const isNumber = (...values: any) => {\n return isType('number', ...values);\n}\n\nexport const isString = (...values: any) => {\n return isType('string', ...values);\n};\n\nexport const isType = (type: string, ...values: any) => {\n for (const value of values) {\n const isType = typeof value === type;\n if (!isType) {\n return false;\n }\n }\n return true;\n}\n\nexport const arithmeticOperationExp = (a: any, b: any, op: (a: any, b: any) => any) => {\n return arithmeticOperation(a.eval(), b.eval(), op);\n}\n\nexport const arithmeticOperation = (A: any, B: any, op: (a: any, b: any) => any): any => {\n if (isNumber(A, B)) {\n return op(A, B); \n }\n if (isNumber(A) && isAnyGeometryType(B)) {\n return arithmeticOperation(turf.point([A, A]).geometry, B, op);\n }\n if (isNumber(B) && isAnyGeometryType(A)) {\n return arithmeticOperation(A, turf.point([B, B]).geometry, op);\n }\n if (isGeometryType(GeometryType.Point, A, B)) {\n return pointOperation(A, B, op);\n }\n if (isGeometryType(GeometryType.LineString, A, B)) {\n return lineStringOperation(A, B, op);\n }\n if (isGeometryType(GeometryType.MultiPoint, A, B)) {\n return multiPointOperation(A, B, op);\n }\n if (isGeometryType(GeometryType.Point, A)) {\n return transform(B, (b => pointOperation(A, b, op)))\n }\n if (isGeometryType(GeometryType.Point, B)) {\n return transform(A, (a => pointOperation(a, B, op)))\n }\n return undefined;\n}\n\nexport const pointOperation = (\n A: any,\n B: any,\n computeFn: (a: number, b: number) => number\n) => {\n return turf.point([\n computeFn(A.coordinates[0], B.coordinates[0]),\n computeFn(A.coordinates[1], B.coordinates[1]),\n ]).geometry;\n}\n\n\nexport const lineStringOperation = (\n A: any,\n B: any,\n computeFn: (a: number, b: number) => number\n) => {\n return turf.lineString(A.coordinates.map((p: number[], index: number) => {\n return [\n computeFn(p[0], B.coordinates[index][0]),\n computeFn(p[1], B.coordinates[index][1]),\n ];\n })).geometry;\n}\n\nexport const multiPointOperation = (\n A: any,\n B: any,\n computeFn: (a: number, b: number) => number\n) => {\n return turf.multiPoint(A.coordinates.map((p: number[], index: number) => {\n return [\n computeFn(p[0], B.coordinates[index][0]),\n computeFn(p[1], B.coordinates[index][1]),\n ];\n })).geometry;\n}\n\nexport class OperationNotSupported extends Error {\n constructor(message: string) {\n super(`Operation not supported: ${message}`);\n }\n}\n\nexport function toString(value: any) {\n try {\n return `${JSON.stringify(value)}`;\n } catch (err) {\n return `${value}`;\n }\n}\n\nexport function transformPoints(coords: any[], coordsMapFn: (g: Point) => any): any {\n if (!!coords) {\n if (Array.isArray(coords)) {\n if (coords.length > 0) {\n const firstElement = coords[0];\n if (Array.isArray(firstElement)) {\n return coords.map((c: any) => transformPoints(c, coordsMapFn));\n } else {\n // coords is a point\n const point = turf.point(coords).geometry;\n return coordsMapFn(point).coordinates;\n }\n \n }\n }\n }\n return coords\n}\n\nexport function transform(geoJson: any, coordsMapFn: (g: Point) => any): any { \n if (!!geoJson) {\n\n if (!!geoJson.features) {\n return {\n ...geoJson,\n features: geoJson.features.map((feature: any) => transform(feature, coordsMapFn))\n };\n }\n\n if (!!geoJson.geometries) {\n return {\n ...geoJson,\n geometries: geoJson.geometries.map((geometry: any) => transform(geometry, coordsMapFn))\n }\n }\n\n const geometry: any = geoJson.geometry;\n if (!!geometry) {\n if (geoJson.coordinates) {\n return {\n ...geoJson,\n geometry: {\n ...geometry,\n coordinates: transformPoints(geometry.coordinates, coordsMapFn)\n }\n }\n }\n }\n\n const coordinates = geoJson.coordinates;\n if (!!coordinates) {\n return {\n ...geoJson,\n coordinates: transformPoints(coordinates, coordsMapFn)\n }\n }\n }\n return geoJson;\n}\n\nexport function convertToGeometry(json: any): any {\n if (json.type === 'Feature') {\n return json.geometry;\n } else if (json.type === 'FeatureCollection') {\n return {\n type: 'GeometryCollection',\n geometries: json.features.map((f: any) => convertToGeometry(f))\n }\n } else if (Array.isArray(json)) {\n return {\n type: 'GeometryCollection',\n geometries: json.map((f: any) => convertToGeometry(f))\n }\n }\n return json;\n}\n\nexport function geometryAccessor(v: any, p: any, params: any[]) {\n const value = v.eval();\n const property = p.sourceString;\n\n if (!isAnyGeometryType(value)) {\n throw new Error(`Expected a geometry type for value \"${v.sourceString}\" but got: ${toString(value)}`);\n }\n\n switch (property.toLocaleLowerCase()) {\n case 'type':\n if (params?.length > 1) {\n throw new Error(`Expected no parameters for \"${property}\" for ${v.sourceString}`)\n }\n return getGeometryType(value, true);\n }\n\n if (isGeometryType(GeometryType.Point, value)) {\n switch (property.toLocaleLowerCase()) {\n case 'x':\n if (params?.length > 0) {\n // setter\n if (params.length === 1) {\n return turf.point([\n params[0],\n value.coordinates[1]\n ]).geometry;\n }\n throw Error(`Expected one value in \"${property}\" setter for \"${v.sourceString}\" but got: ${toString(params)}`)\n }\n // getter\n return value.coordinates[0];\n case 'y':\n if (params?.length > 0) {\n // setter\n if (params.length === 1) {\n return turf.point([\n value.coordinates[0],\n params[0]\n ]).geometry;\n }\n throw Error(`Expected one value in \"${property}\" setter for \"${v.sourceString}\" but got: ${toString(params)}`)\n }\n // getter\n return value.coordinates[1];\n }\n } else if (isGeometryType(GeometryType.GeometryCollection, value)) {\n switch (property.toLocaleLowerCase()) {\n case 'geometryn':\n if (params.length === 1) {\n const index = parseInt(params[0]);\n return value.geometries[index];\n }\n throw Error(`Expected one value in \"${property}\" setter for \"${v.sourceString}\" but got: ${toString(params)}`)\n case 'numgeometries':\n return value.geometries.length;\n }\n } else if (isGeometryType(GeometryType.LineString, value)) {\n switch (property.toLocaleLowerCase()) {\n case 'pointn':\n if (params.length === 1) {\n const index = parseInt(params[0]);\n return turf.point(value.coordinates[index]).geometry;\n }\n throw Error(`Expected one value in \"${property}\" setter for \"${v.sourceString}\" but got: ${toString(params)}`)\n case 'numpoints':\n return value.coordinates.length;\n }\n }\n\n throw new Error(`Property \"${property}\" not accessible on object: ${toString(value)}`);\n\n}","export interface ScopeBindings {\n [identifier: string]: any;\n}\n\nexport interface Metadata {\n public: boolean;\n}\n\nexport interface ScopeBindingMetadata {\n [identifier: string]: {\n public?: boolean\n };\n}\n\nexport class Scope {\n\n bindings: ScopeBindings = {};\n availableBindings: ScopeBindings = {};\n private metadata: ScopeBindingMetadata = {};\n constructor(\n private parent?: Scope,\n public readonly level = 0,\n defaultBindings: ScopeBindings = {}\n ) {\n for (const identifier in defaultBindings) {\n if (defaultBindings.hasOwnProperty(identifier)) {\n this.store(identifier, defaultBindings[identifier]);\n }\n }\n }\n\n store(identifier: string, value: any, metadata?: Metadata, searchParentChain = true) {\n let scope: Scope = this;\n if (searchParentChain) {\n const resolvedScope = this.resolveScope(identifier);\n if (resolvedScope) {\n scope = resolvedScope;\n }\n scope.store(identifier, value, metadata, false);\n return;\n }\n this.bindings[identifier] = value;\n if (metadata) {\n this.metadata[identifier] = metadata;\n }\n };\n\n resolveScope(identifier: string): Scope | undefined {\n if (this.bindings.hasOwnProperty(identifier)) {\n return this;\n }\n\n if (this.parent) {\n return this.parent.resolveScope(identifier)\n }\n\n return undefined;\n }\n\n resolve(identifier: string): any {\n const scope = this.resolveScope(identifier) ?? this\n return scope.bindings[identifier];\n };\n\n push(extraBindings?: ScopeBindings): Scope {\n return new Scope(this, this.level + 1, extraBindings);\n }\n\n pop(additionalBindings?: ScopeBindings): Scope | undefined {\n const exportedBindings: ScopeBindings = {\n ...additionalBindings\n };\n for (const identifier in this.metadata) {\n const metadata = this.metadata[identifier];\n if (metadata && metadata.public) {\n exportedBindings[identifier] = this.bindings[identifier]\n }\n }\n this.parent?.import(exportedBindings)\n return this.parent;\n }\n\n import(scopeBindings: ScopeBindings) {\n this.availableBindings = {\n ...this.availableBindings,\n ...scopeBindings\n };\n }\n\n useImports(selectedIdentifiers?: string[]) {\n let selectedBindings: ScopeBindings = {};\n if (selectedIdentifiers) {\n selectedIdentifiers.forEach(selectedIdentifier => {\n selectedBindings[selectedIdentifier] = this.availableBindings[selectedIdentifier]\n });\n } else {\n selectedBindings = this.availableBindings\n }\n this.availableBindings = {};\n return selectedBindings;\n }\n\n useNamedImports(selectedIdentifiers: string[]) {\n let selectedBindings: ScopeBindings = {};\n if (selectedIdentifiers) {\n selectedIdentifiers.forEach(selectedIdentifier => {\n selectedBindings[selectedIdentifier] = this.availableBindings[selectedIdentifier]\n });\n }\n this.bindings = {\n ...this.bindings,\n ...selectedBindings,\n };\n return selectedBindings;\n }\n}","import * as turf from '@turf/turf';\n\nimport { OperationNotSupported, isGeometryType, toString, transform } from './helpers';\n\nimport { GeometryType } from './types';\nimport { Point } from 'geojson';\nimport booleanEqual from \"@turf/boolean-equal\"\n\nexport namespace BuiltInFunctions {\n\n const FlattenHelper = (value: any) => {\n const flattenedValues: any[] = [];\n if (!!value) {\n if (value?.type === GeometryType.GeometryCollection) {\n for (const item of value.geometries) {\n flattenedValues.push(...FlattenHelper(item));\n }\n } else {\n flattenedValues.push(value);\n }\n }\n return flattenedValues;\n };\n\n export const Flatten = (value: any) => {\n if (value?.type === GeometryType.GeometryCollection) {\n return turf.geometryCollection(FlattenHelper(value)).geometry;\n }\n return value;\n };\n\n export const PointCircle = (radius: number, count: number) => {\n const circlePoints: any[] = [];\n const angleIncrement = (2 * Math.PI) / count;\n for (let i = 0; i < count; i++) {\n const angle = i * angleIncrement;\n const x = radius * Math.cos(angle);\n const y = radius * Math.sin(angle);\n circlePoints.push(turf.point([x, y]).geometry);\n }\n return turf.geometryCollection(circlePoints).geometry;\n };\n\n export const PointGrid = (x: number, y: number, spacing = 1) => {\n const points: any[] = [];\n for (let i=0; i<x; i++) {\n for (let j=0; j<y; j++) {\n const point = turf.point([i * spacing, j * spacing]).geometry;\n points.push(point);\n }\n }\n return turf.geometryCollection(points).geometry;\n }\n\n const getPointsList = (value: any) => {\n let points;\n if (isGeometryType(GeometryType.GeometryCollection, value)) {\n points = value?.geometries.map((f: any) => f.coordinates);\n } else if (\n isGeometryType(GeometryType.LineString, value) ||\n isGeometryType(GeometryType.MultiPoint, value)\n ) {\n points = value?.coordinates;\n }\n if (points) {\n return points;\n }\n throw new Error(\"Expected geometry with points list\");\n }\n\n export const ToLineString = (value: any) => {\n const pointsList = getPointsList(value);\n return turf.lineString(pointsList).geometry;\n };\n\n export const ToMultiPoint = (value: any) => {\n const pointsList = getPointsList(value);\n return turf.multiPoint(pointsList).geometry;\n };\n\n export const ToPolygon = (value: any) => {\n const pointsList = getPointsList(value);\n // Auto-close polygon\n if (pointsList.length > 0) {\n if (!booleanEqual(\n turf.point(pointsList[0]).geometry,\n turf.point(pointsList[pointsList.length - 1]).geometry\n )) {\n pointsList.push(pointsList[0]);\n }\n }\n return turf.polygon([pointsList]).geometry;\n };\n\n export const ToGeometryCollection = (value: any) => {\n const pointsList = getPointsList(value);\n return turf.geometryCollection(pointsList.map((p: any) => turf.point(p).geometry)).geometry;\n };\n\n export const Rotate = (angleDegrees: number, origin: Point = turf.point([0, 0]).geometry, geometry: any) => {\n return transform(geometry, (p: Point) => {\n // Convert angle from degrees to radians\n const angleRadians = (angleDegrees * Math.PI) / -180;\n const originX = origin.coordinates[0];\n const originY = origin.coordinates[1];\n const x = p.coordinates[0];\n const y = p.coordinates[1];\n\n // Calculate the distance from the origin to the point\n const dx = x - originX;\n const dy = y - originY;\n\n // Perform the rotation\n const newX = originX + dx * Math.cos(angleRadians) - dy * Math.sin(angleRadians);\n const newY = originY + dx * Math.sin(angleRadians) + dy * Math.cos(angleRadians);\n\n return turf.point([newX, newY]).geometry;\n });\n }\n\n export const Round = (precision = 0, val: any): any => {\n if (typeof val === 'number') {\n return +val.toFixed(precision);\n }\n if (isGeometryType(GeometryType.Point, val)) {\n const coords = val.coordinates;\n return turf.point([\n Round(precision, coords[0]),\n Round(precision, coords[1]),\n ]).geometry;\n }\n throw new OperationNotSupported(`Unable to round value: ${toString(val)}`)\n }\n}\n","export const GRAMMAR = String.raw`\nWAEL {\n\n/***************\n * WAEL syntax *\n ***************/\n\n// Top-level expressions\nTopLevel = ScopedExpressions TopLevelEnd\nTopLevelEnd = ExpressionDelimiter | end \n\n// Comments\ncomment = \"#\" commentSpace* commentText* commentSpace* commentEnd\ncommentText = ~commentEnd any\ncommentEnd = \"\\n\" | end\ncommentSpace = \" \"\n\n// Expression structure\nScopedExpressions = ListOf<GeneralExpression, ExpressionDelimiter> --list\n | GeneralExpression\nGeneralExpression = GeneralExpression comment --rightComment\n | comment GeneralExpression --leftComment\n | AssignmentExp\n | AssignableExpression \n | comment \nExpressionDelimiter = expressionDelimiter\nexpressionDelimiter = \";\"\n\n// Imports\nImportExpression = AccessibleExp<ImportExpressionType>\nImportExpressionType = ImportUsingExpression | ImportAllExpression\nImportUsingExpression = ImportEitherExpression usingKeyword ImportUsingParameters\nImportUsingParameters = FunctionParameters | ImportUsingAllParams\nImportUsingAllParams = Paren<\"*\">\nImportAllExpression = ImportEitherExpression\nImportEitherExpression = ImportExternalExp | ImportFunctionExp\nImportExternalExp = importKeyword Paren<ImportExternalParamExp>\nImportExternalParamExp = stringLiteralExp | Identifier\nImportFunctionExp = importKeyword Paren<FunctionCallExp>\nimportKeyword = caseInsensitive<\"import\">\nusingKeyword = caseInsensitive<\"using\">\n\n// Exports\nexportKeyword = caseInsensitive<\"export\">\n\n// Variables\nletKeyword = caseInsensitive<\"let\">\nAssignmentExp = exportKeyword? letKeyword? Identifier assignmentOperator AssignableExpression \nAssignableExpression = \n | ImportExpression\n | NonArithmeticAssignableExpression\n | ArithmeticAssignableExpression\nNonArithmeticAssignableExpression = \n | OperationExp\n | stringLiteralExp\nArithmeticAssignableExpression = Arithmetic<AssignableExpressionForArithmetic>\nAssignableExpressionForArithmetic = \n | BooleanResultExp\n | IfThenElseExp\n | ComputedExp\n | FunctionTextExp\n | NumberExp\n | AccessibleExp<GeometryExp>\n | BooleanValue\n | Paren<OperationExp> \nassignmentOperator = \"=\"\nComputedValue<Type> = Type | ComputedExp\nComputedExp = AccessibleExp<ComputedPrimitive>\nComputedPrimitive = FunctionCallExp | Identifier\n\nAccessibleExp<Type> = \n | Type accessorOperator Identifier Invocation? --method\n |