wael-lib
Version:
Well-Known Text Arithmetic Expression Language
1 lines • 160 kB
Source Map (JSON)
{"version":3,"sources":["../../package.json","../../scripts/wael.ts","../../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","../../node_modules/geojson-equality-ts/index.ts","../../node_modules/@turf/helpers/index.ts","../../node_modules/@turf/invariant/index.ts","../../node_modules/@turf/clean-coords/index.ts","../../node_modules/@turf/boolean-equal/index.ts","../../src/interpreter/grammar.ts","../../src/lib.ts"],"sourcesContent":["{\n \"name\": \"wael-lib\",\n \"version\": \"0.0.14\",\n \"description\": \"Well-Known Text Arithmetic Expression Language\",\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}\n","import * as fs from 'fs';\n\nimport { Options, OutputFormat, Wael } from \"../src/main\";\n\nimport { Interpreter } from '../src/interpreter/interpreter';\nimport chalk from 'chalk';\nimport { toString } from '../src/interpreter/helpers';\nimport yargs from 'yargs'\n\nconst readline = require('readline');\nconst packageJson = require('../package.json')\n\nconst input_files = 'input_files';\nconst version = packageJson.version;\nconst args = (yargs as any).command('$0', `${packageJson.description}\\nVersion: ${version}`)\n .positional(input_files, {\n array: true,\n description: 'List of 1 or more input files'\n })\n .option('format', {\n choices: Object.values(OutputFormat) as OutputFormat[],\n description: 'Output format'\n })\n .option('geojson', {\n alias: 'g',\n boolean: true,\n description: 'Output as GeoJSON'\n })\n .option('outputNonGeoJSON', {\n alias: 'a',\n boolean: true,\n description: 'Output non-GeoJSON results'\n })\n .option('interactive', {\n alias: 'i',\n boolean: true,\n description: 'Launch an interactive session'\n })\n .option('evaluate', {\n alias: ['e', 'pre-evaluate'],\n string: true,\n description: `Evaluate the specified script text before [${input_files}]`\n })\n .option('post-evaluate', {\n string: true,\n description: `Evaluate the specified script text after [${input_files}]`\n })\n .option('bind-import', {\n alias: 'b',\n type: 'array',\n string: true,\n description: 'Bind imported data to a variable'\n })\n .version(version)\n .parseSync();\n\nconst getJsonString = (json: any) => {\n return JSON.stringify(json);\n}\n\n\nconst evaluateScript = args.evaluate;\nconst postEvaluateScript = args.postEvaluate;\nconst inputFiles = args._;\nconst isInteractive = args.interactive;\nconst bindImports = args.bindImport;\nconst highlightText = chalk.hex(`#1f91cf`);\nconst errorText = chalk.hex(`#bd3131`);\nconst subtleText = chalk.hex(`#777`);\n\nconst outputFormat = args.geojson ? OutputFormat.GeoJSON : \n args.format ? args.format : OutputFormat.WKT;\n\nlet outputNonGeoJSON = args.outputNonGeoJSON;\nif (outputNonGeoJSON === undefined && isInteractive) {\n outputNonGeoJSON = true;\n}\nconst options: Options = {\n outputFormat,\n scope: Interpreter.createGlobalScope(),\n outputNonGeoJSON\n};\n\nconst wael = new Wael(options);\nlet result: any;\nlet hasEvaluated = false;\n\nconst errorExit = (message: string) => {\n console.log(errorText(message));\n process.exit(-1);\n}\n\nconst getSectionHeading = (label: string) => {\n const cols = process.stdout.columns;\n const filler = cols - label.length;\n const splitCount = (filler % 2 === 0 ? filler : filler - 1) / 2;\n const splitLabel = '-'.repeat(splitCount);\n let line = splitLabel + label + splitLabel;\n if (line.length < cols) {\n line += '-';\n }\n return line;\n}\n\nconst printOutput = (output: string) => {\n let result = options.outputFormat === OutputFormat.GeoJSON ? getJsonString(output) : output;\n console.log(subtleText(`${result}`));\n};\n\nconst prompt = (details: string = '', script = '') => {\n const line = getSectionHeading(` [${wael.getEvaluationCount()}] ${details}`);\n console.log(highlightText(line))\n if (!!script) {\n console.log(script);\n }\n}\n\nconst evaluate = (script: string, heading: string) => {\n if (isInteractive) {\n prompt(heading, script);\n }\n let result = wael.evaluate(script);\n if (isInteractive) {\n printOutput(result);\n }\n return result;\n}\n\nconst EXIT_CMD = `exit()`;\nconst END_TOKEN = `;;`;\n\nif (isInteractive) {\nconst instructions = \n`Starting WAEL interactive session...\n\nEnd expressions with ;; to evaluate.\nThe last evaluation result is stored in the ${Wael.IDENTIFIER_LAST} variable.\nPrevious evaluation results are stored in indexed variables $0, $1, $2, ...\n`;\nconsole.log(subtleText(instructions));\n}\n\n\n// Evaluate import bindings\nif (bindImports) {\n for (const bindImport of bindImports) {\n const [identifier, uri] = bindImport.split('=');\n if (identifier && uri) {\n try {\n result = evaluate(`${identifier} = import('${uri.trim()}')`, `import ${identifier}`);\n } catch (err: any) {\n errorExit(`Unable to evaluate import binding \"${bindImport}\" \\n\\t${err.message}`);\n }\n } else {\n errorExit(`Unable to evaluate import binding \"${bindImport}\"`);\n }\n }\n}\n\n// Evaluate initial script\nif (evaluateScript) {\n try {\n result = evaluate(evaluateScript, `pre-evaluate`);\n } catch (err: any) {\n errorExit(`Unable to evaluate pre-evaluate script: \\n\\t${err}`);\n }\n hasEvaluated = true;\n}\n\n// Evaluate files\nif (inputFiles && inputFiles.length > 0) {\n inputFiles.forEach((inputFile: any) => {\n const input = fs.readFileSync(inputFile, 'utf-8');\n result = evaluate(input, inputFile.toString())\n hasEvaluated = true;\n if (options.outputFormat === OutputFormat.GeoJSON) {\n try {\n result = getJsonString(result);\n } catch(err) {\n // return raw output\n }\n }\n });\n}\n\n// Evaluate initial script\nif (postEvaluateScript) {\n try {\n result = evaluate(postEvaluateScript, `post-evaluate`);\n } catch (err: any) {\n errorExit(`Unable to evaluate 'post-evaluate' script: \\n\\t${err}`);\n }\n hasEvaluated = true;\n}\n\n// Run interactive mode\nif (isInteractive) {\n let currentInput = ``;\n\n prompt();\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n prompt: '',\n terminal: true\n });\n \n rl.on('line', (line: string) => {\n if (line.toLocaleLowerCase() === EXIT_CMD) {\n rl.close();\n } else {\n currentInput += `${line.trim()}\\n`;\n const inputEndIndex = currentInput.indexOf(END_TOKEN);\n if (inputEndIndex >= 0) {\n try {\n let result = wael.evaluate(currentInput.substring(0, inputEndIndex), options);\n printOutput(result);\n } catch (err) {\n console.error(errorText(err));\n }\n prompt();\n currentInput = '';\n }\n }\n });\n \n rl.once('close', () => {\n // end of input\n });\n} else {\n if (hasEvaluated) {\n if (typeof result !== 'undefined') {\n if (typeof result === 'object') {\n result = toString(result); \n }\n console.log(result);\n }\n } else {\n yargs.showHelp();\n }\n}\n","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.me