UNPKG

wael-lib

Version:

Well-Known Text Arithmetic Expression Language

1 lines 94.7 kB
{"version":3,"sources":["../../src/main.ts","../../src/interpreter/interpreter.ts","../../src/interpreter/types.ts","../../src/interpreter/helpers.ts","../../package.json","../../src/interpreter/scope.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\";\nimport { objectToString } from './interpreter/helpers';\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 workingDirectory?: string;\n useStdLib?: 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 private history: { [identifier: string]: any } = {};\n static IDENTIFIER_LAST = '$?';\n static IDENTIFIER_OPTIONS = '$OPTIONS';\n static IDENTIFIER_HISTORY = '$HISTORY';\n\n constructor(\n initialOptions?: Options\n ) {\n this.options = {\n ...DEFAULT_OPTIONS,\n ...initialOptions,\n scope: initialOptions?.scope ?? Interpreter.createGlobalScope(),\n };\n\n const optionsVariable = {\n ...this.options\n };\n delete optionsVariable.scope;\n this.options.scope?.store(Wael.IDENTIFIER_OPTIONS, this.options);\n this.options.scope?.store(Wael.IDENTIFIER_HISTORY, this.history);\n\n // Import standard library\n Interpreter.evaluateInput(`${STD_LIB}`, this.options.scope);\n if (this.options?.useStdLib) {\n this.useStdLib();\n }\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 if (!this.options?.useStdLib && overrideOptions?.useStdLib) {\n this.useStdLib();\n }\n\n const result = Interpreter.evaluateInput(input, options.scope, options.workingDirectory);\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 this.history[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 private useStdLib() {\n Interpreter.evaluateInput(`Use(StdLib()) With (*)`, this.options.scope)\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 (!result?.hasOwnProperty('toString')) {\n if (typeof result === 'object') {\n return objectToString(result);\n }\n if (typeof result === 'string') {\n return `\"${result}\"`;\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 generateGeometries,\n geometryAccessor,\n getArrayLikeItems,\n getGeometryType,\n isAnyGeometryType,\n isGeometryType,\n isNumber,\n objectToString,\n toString,\n transform\n} from './helpers';\nimport { Scope, ScopeBindings } from './scope';\n\nimport { GRAMMAR } from './grammar';\nimport path from 'path';\nimport { readFileSync } from 'fs';\nimport syncFetch from 'sync-fetch'\n\nconst grammarString = GRAMMAR;\n\nexport namespace Interpreter {\n\n export const IMPORT_USING_ALL = '*';\n export const DEFAULT_EXPORT_BINDING = 'export'\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\n export const createGlobalScope = () => new Scope(undefined, undefined, STANDARD_LIBRARY);\n\n export function evaluateInput(input: string, initialScope?: Scope, workingDirectory = '.'): 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\n // Return function import object if populated\n const importObj = currentScope.useImports();\n if (importObj && Object.keys(importObj).length > 0) {\n if ((ret ?? null) !== null) {\n importObj[DEFAULT_EXPORT_BINDING] = ret\n }\n importObj.toString = function () {\n return objectToString(importObj);\n }\n return importObj;\n }\n\n // Otherwise return evaluation value\n return ret\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(path.join(workingDirectory, uri), 'utf8');\n }\n currentScope = currentScope.push();\n let ret = undefined;\n try {\n ret = convertToGeometry(JSON.parse(data));\n } catch {\n try {\n const libRet = evaluateInput(data, currentScope, workingDirectory);\n if (libRet) {\n ret = libRet\n }\n } catch {\n throw new Error(`Unable to import file: ${uri}`);\n }\n }\n currentScope = currentScope.pop() || GLOBAL_SCOPE;\n return ret;\n },\n ImportFunctionExp(_keyword, importFn) {\n return 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 }\n throw new Error(`Operator not supported: ${operator}`);\n },\n LogicalExp(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 \"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 GenerateSymbolExp(numExp, _symbol, valueExp) {\n return generateGeometries(numExp, valueExp);\n },\n FunctionCallExp(callable, p) {\n let fn = callable.eval();\n const params = p.eval();\n\n if ((fn ?? null) === null) {\n throw new Error(`${callable.sourceString} is: ${fn}`);\n }\n\n if (\n typeof fn === 'object' &&\n ((fn[DEFAULT_EXPORT_BINDING] ?? null) !== null)\n ) {\n fn = fn[DEFAULT_EXPORT_BINDING];\n }\n\n if (typeof fn !== 'function') {\n return fn;\n }\n\n fn.context = {\n currentScope\n }\n\n return fn(...params);\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 let isSpread = false;\n if (!Array.isArray(params)) {\n isSpread = params?.isSpread\n }\n params = Array.isArray(params) ? params : [params];\n let fnScope = currentScope;\n const fn = (...values: any[]) => {\n\n // Create new scope per function call.\n const contextScope = (fn as any)?.context?.currentScope || currentScope;\n currentScope = contextScope.push();\n currentScope.capture(fnScope);\n\n if (isSpread) {\n const geometries = turf.geometryCollection(values).geometry\n currentScope.store(params[0].identifier, geometries, undefined, false)\n } else {\n params.forEach((paramName: any, index: number) => {\n currentScope.store(paramName, values[index], undefined, false)\n });\n }\n const ret = body.eval();\n currentScope = currentScope.pop() || GLOBAL_SCOPE;\n return ret;\n };\n fn.toString = function () { return `Function(${p.sourceString} => ${body.sourceString})` }\n fn.toStringShort = function () { return `Function(${p.sourceString} => (...))` }\n return fn;\n },\n FunctionParameters_spread(_leftParen, _operator, identifier, _rightParen) {\n return {\n identifier: identifier.sourceString,\n isSpread: true\n };\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 GeometryCollectionParameters(list) {\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(multiPoint) {\n return multiPoint.eval();\n },\n MultiPoint(list) {\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 PointListLiteral(list) {\n return list.asIteration().children.map(c => c.eval());\n },\n PointListSpread(_op, collection) {\n return getArrayLikeItems(collection.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 throw new Error(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\nconst INDENT = ' ';\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\nexport const objectToString = (obj: any, includeHistory = true) => {\n const bindings = Object.keys(obj ?? {})\n .filter(identifier => {\n return identifier !== 'toString' &&\n (includeHistory || !identifier?.startsWith('$')) &&\n (obj[identifier] ?? null) !== null\n })\n .map(identifier => {\n const value = obj[identifier];\n let strValue;\n if (value?.toStringShort) {\n strValue = value.toStringShort();\n } else if (typeof value === 'object') {\n if (isAnyGeometryType(value)) {\n strValue = value?.type;\n } else {\n strValue = `Module(...)`;\n }\n } else if (typeof value === 'string') {\n strValue = `\"${value}\"`\n } else if (typeof value === 'function') {\n strValue = `<Native Function>`\n } else {\n strValue = value;\n }\n return `${INDENT}${identifier} = ${strValue}`;\n });\n return `Module(\\n${bindings.join('\\n')}\\n)`;\n}\n\nexport const generateGeometries = (numExp: any, valueExp: any) => {\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, num);\n while (condition) {\n const result = mapFn(i);\n if (result !== undefined) {\n if (!isAnyGeometryType(result)) {\n throw new Error(`Expected geometry type return value but got: ${toString(result)}`);\n }\n items.push(result);\n }\n condition = num(++i);\n }\n } else {\n for (let i = 0; i < num; i++) {\n const result = mapFn(i, num);\n if (result !== undefined) {\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 }\n return turf.geometryCollection(items).geometry;\n}\n","{\n \"name\": \"wael-lib\",\n \"version\": \"0.0.25\",\n \"description\": \"Well-Known Text Arithmetic Expression Language\",\n \"bin\": {\n \"wael\": \"./dist/wael/wael.cjs\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/anthonydgj/wael.git\"\n },\n \"files\": [\n \"dist\",\n \"examples\"\n ],\n \"browser\": {\n \"fs\": false,\n \"path\": false,\n \"os\": false\n },\n \"type\": \"commonjs\",\n \"main\": \"dist/cjs/index.cjs\",\n \"module\": \"dist/esm/index.mjs\",\n \"types\": \"dist/cjs/index.d.ts\",\n \"exports\": {\n \"./package.json\": \"./package.json\",\n \".\": {\n \"import\": {\n \"types\": \"./dist/esm/index.d.mts\",\n \"default\": \"./dist/esm/index.mjs\"\n },\n \"require\": {\n \"types\": \"./dist/cjs/index.d.ts\",\n \"default\": \"./dist/cjs/index.cjs\"\n }\n }\n },\n \"scripts\": {\n \"build\": \"tsup --config ./tsup.config.ts\",\n \"build-all\": \"npm run build-binary && npm run build-release-binaries\",\n \"build-binary\": \"npm run build && pkg ./dist/wael/wael.cjs -t=latest --no-bytecode --public-packages \\\"*\\\" --public --out-path dist/bin/\",\n \"build-release-binaries\": \"pkg ./dist/wael/wael.cjs -t=latest,latest-macos-arm64,latest-linuxstatic-x64,latest-win-x64 --no-bytecode --public-packages \\\"*\\\" --public --out-path dist/bin/release/\",\n \"test\": \"jest\",\n \"publish-lib\": \"npm run build && npm test && npm publish\"\n },\n \"author\": \"AnthonyDGJ\",\n \"license\": \"MIT\",\n \"devDependencies\": {\n \"@types/geojson\": \"^7946.0.16\",\n \"@types/jest\": \"^29.5.14\",\n \"@types/sync-fetch\": \"^0.4.3\",\n \"@types/wellknown\": \"^0.5.8\",\n \"jest\": \"^29.7.0\",\n \"pkg\": \"^5.8.1\",\n \"ts-jest\": \"^29.3.2\",\n \"ts-node\": \"^10.9.2\",\n \"tsup\": \"^8.4.0\",\n \"typescript\": \"^5.8.3\"\n },\n \"dependencies\": {\n \"@turf/turf\": \"^7.2.0\",\n \"chalk\": \"^4.1.2\",\n \"fs\": \"^0.0.1-security\",\n \"ohm-js\": \"^17.1.0\",\n \"sync-fetch\": \"^0.6.0-2\",\n \"tslib\": \"^2.8.1\",\n \"wellknown\": \"^0.5.0\",\n \"yargs\": \"^17.7.2\"\n },\n \"keywords\": [\n \"wkt\",\n \"well-known text\",\n \"ohm\",\n \"dsl\",\n \"domain-specific language\",\n \"geospatial\"\n ]\n}","import { objectToString } from \"./helpers\";\nimport packageJson from '../../package.json'\n\nexport 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 public readonly IDENTIFIER_SCOPE = '$SCOPE';\n public readonly IDENTIFIER_VERSION = '$VERSION';\n\n bindings: ScopeBindings = {};\n availableBindings: ScopeBindings = {};\n private metadata: ScopeBindingMetadata = {};\n private closures: Scope[] = [];\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 for (const closure of this.closures) {\n if (closure.bindings.hasOwnProperty(identifier)) {\n return closure;\n }\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 const binding = scope.bindings[identifier];\n if (!binding) {\n // Handle dynamic bindings\n if (identifier === this.IDENTIFIER_SCOPE) {\n const currentBindings = {\n ...scope.bindings\n };\n currentBindings.toString = function () { return objectToString(currentBindings, false) }\n return currentBindings;\n }\n\n if (identifier === this.IDENTIFIER_VERSION) {\n return packageJson?.version || 'UNKNOWN';\n }\n }\n return binding;\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 ...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 delete this.availableBindings[selectedIdentifier]\n });\n } else {\n selectedBindings = this.availableBindings\n this.availableBindings = {}\n }\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 this.store(selectedIdentifier, this.availableBindings[selectedIdentifier])\n delete this.availableBindings[selectedIdentifier]\n });\n }\n return selectedBindings;\n }\n\n capture(capturedScope: Scope) {\n this.closures.push(capturedScope)\n }\n release() {\n this.closures.pop()\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\nScope