diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..af0f0c3 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015"] +} \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..198dae8 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,147 @@ +{ + "extends": "eslint:recommended", + "env": { + "node": true, + "mocha": true, + "es6": true + }, + "parser": "babel-eslint", + "rules": { + "array-bracket-spacing": [ + 2, + "never" + ], + "brace-style": [ + 2, + "stroustrup" + ], + "consistent-return": 0, + "indent": [ + 2, + "tab", + { + "SwitchCase": 1 + } + ], + "no-multiple-empty-lines": [ + 2, + { + "max": 3 + } + ], + "no-use-before-define": 0, + "one-var": [ + 2, + "never" + ], + "quote-props": [ + 1, + "as-needed" + ], + "quotes": [ + 2, + "single" + ], + "space-after-keywords": 0, + "space-before-function-paren": [ + 2, + { + "anonymous": "never", + "named": "never" + } + ], + "space-in-parens": [ + 2, + "never" + ], + "strict": [ + 2, + "global" + ], + "curly": [ + 2, + "multi-line" + ], + "eol-last": 2, + "key-spacing": [ + 2, + { + "beforeColon": false, + "afterColon": true + } + ], + "no-debugger": 1, + "no-eval": 0, + "no-with": 2, + "space-infix-ops": 0, + "dot-notation": [ + 2, + { + "allowKeywords": true + } + ], + "eqeqeq": 2, + "no-alert": 2, + "no-caller": 2, + "no-console": 1, + "no-constant-condition": 1, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 0, + "no-loop-func": 0, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-native-reassign": 2, + "no-new": 0, + "no-new-func": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-proto": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-sequences": 2, + "no-unused-vars": 1, + "no-unused-expressions": 2, + "yoda": [ + 1, + "always", + { + "onlyEquality": true + } + ], + "no-shadow": 2, + "no-shadow-restricted-names": 2, + "no-undef-init": 2, + "camelcase": 0, + "comma-spacing": 2, + "new-cap": 0, + "new-parens": 2, + "no-array-constructor": 2, + "no-extra-parens": 0, + "no-new-object": 2, + "no-spaced-func": 2, + "no-trailing-spaces": 1, + "no-underscore-dangle": [ + 2, + { + "allow": ["__proto__", "_private"] + } + ], + "comma-dangle": 0, + "semi": 2, + "semi-spacing": [ + 2, + { + "before": false, + "after": true + } + ], + }, + "ecmaFeatures": { + "modules": true + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d24d568 --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + + + +# Directories + +# auxillary git repositories +.git-* + +# user config for binaries +config + +# data files +data + +# compiled distribution +dist + +# external binaries +ext + +# runtime logs +logs + +# dependencies +node_modules + +# publication +publish + +# developer scrap files +scrap + +# +test + +# +tools + + +# user's gulp config file +config.user.js + +# kill scripts +kill-* + +# ignore files +*.ignore diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..99dd037 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2016, Blake Regalia + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..672991e --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# VOLT + + +This node.js module is an implementation of the **V**olt **O**ntoloy and **L**inked-data **T**echnology. + +## What this module is +This module is for the low-level processing of SPARQL queries, compiling procedures to javascript, evaluating procedures in memory, etc. *and* compiling `.volt` source files to their serialized `.ttl` form. + +## The "Proxy" +An HTTP proxy for VOLT is available as [volt-proxy.js](blake-regalia/volt-proxy.js), which simply wraps this module using the express library. + +## Demo +If you simply want to connect VOLT to your local triple store and execute SPARQL queries over HTTP, go check out [volt-demo.js](blake-regalia/volt-demo.js) which launches a local webapp with a SPARQL interface for submitting queries directly to the VOLT proxy. + +## Install +```sh +$ npm install volt +``` + +## Usage + +```js +const volt = require('volt'); + +// create volt instance +let volt_query = volt({ + plugins: +}); + +// issue sparql query +volt_query('ask {:A :b :C}', (h_sparql_results) => { + // ... +}); + +// use the library to compile .volt => .ttl +let h_compiled_procedures = volt.compile({ + code: fs.readFileSync('source.volt'), +}); +``` + +## Development + +```sh +$ gulp develop +``` diff --git a/config.module.js b/config.module.js new file mode 100644 index 0000000..da4cf38 --- /dev/null +++ b/config.module.js @@ -0,0 +1,11 @@ + +export default { + src: 'lib', + dest: 'dist', + targets: { + volt: 'js', + compiler: 'language', + 'syntax-lex': 'syntax-highlighting', + 'syntax-volt': 'syntax-highlighting', + }, +}; diff --git a/debug b/debug new file mode 100755 index 0000000..5789f57 --- /dev/null +++ b/debug @@ -0,0 +1,6 @@ +#!/bin/bash +dir=$1; shift +script=$1; shift +nodemon --delay 2 -x 'gulp $dir && node-debug --cli --debug-brk --no-preload' \ + --watch "lib/$dir" \ + "dist/$dir/$script.js" $@ diff --git a/debug-language b/debug-language new file mode 100755 index 0000000..9ec706d --- /dev/null +++ b/debug-language @@ -0,0 +1,7 @@ +#!/bin/bash +dir=$1; shift +script=$1; shift +nodemon --delay 2 -x 'gulp $dir && node-debug --cli --debug-brk --no-preload --save-live-edit' \ + --watch "lib/$dir" \ + -e js,jison,jisonlex \ + "dist/$dir/$script.js" $@ diff --git a/gulp/ace-mode.js b/gulp/ace-mode.js new file mode 100644 index 0000000..b65b4c5 --- /dev/null +++ b/gulp/ace-mode.js @@ -0,0 +1,50 @@ + +import path from 'path'; +import child_process from 'child_process'; +import glob from 'glob'; + +// module +export default (gulp, $, config={}) => { + + // + const p_ace = (config.paths && config.paths.ace) || './node_modules/ace'; + + // task maker + return function sublime(s_dir, s_task, p_src, p_dest) { + + // generate syntax highlighter + gulp.task(s_task, [this.task('tm-language')], (cb) => { + + // + return glob(p_dest+'/**/*.tmLanguage', (e_glob, a_files) => { + if(e_glob) cb(e_glob); + + // + a_files.map((p_file) => { + + // + let s_mode = path.basename(p_file, '.tmLanguage'); + + // convert language file + child_process.spawnSync('node', [`${p_ace}/tool/tmlanguage.js`, p_file]); + + // copy ace-mode files + gulp.src(`${p_ace}/lib/ace/mode/${s_mode}*.js`) + + // replace + .pipe($.replace(/require\("(\.\.\/)/g, 'ace.require("ace/')) + .pipe($.replace(/require\("(\.\/)/g, 'ace.require("ace/mode/')) + // .pipe($.replace(/define\(/, `ace.define("ace/mode/volt_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],`)) + + // concat + .pipe($.concat('mode-'+s_mode+'.js')) + + .pipe($.debug()) + + // to output directory + .pipe(gulp.dest(p_dest+'/ace')); + }); + }); + }); + }; +}; diff --git a/gulp/clean.js b/gulp/clean.js new file mode 100644 index 0000000..3780af2 --- /dev/null +++ b/gulp/clean.js @@ -0,0 +1,10 @@ + +import del from 'del'; + +export default function clean(gulp) { + return (s_dir, s_task, p_src, p_dest) => { + gulp.task(s_task, () => { + return del.sync([p_dest]); + }); + }; +} diff --git a/gulp/develop.js b/gulp/develop.js new file mode 100644 index 0000000..3c780a0 --- /dev/null +++ b/gulp/develop.js @@ -0,0 +1,16 @@ + +// module +export default (gulp) => { + + // make develop task + return function develop(s_dir, s_task, p_src) { + + // make build task name + let s_build_task = this.task(this.args[0]); + + // register develop task + gulp.task(s_task, [s_build_task], () => { + gulp.watch(p_src+'/**/*', [s_build_task]); + }); + }; +}; diff --git a/gulp/jison.js b/gulp/jison.js new file mode 100644 index 0000000..75ac8f9 --- /dev/null +++ b/gulp/jison.js @@ -0,0 +1,89 @@ + +import fs from 'fs'; +import glob from 'glob'; +import through from 'through2'; + +import {Generator} from 'jison'; +import ebnf_parser from 'ebnf-parser'; +import lex_parser from 'lex-parser'; + + +// module +export default (gulp, $) => { + + // make jison task + return function jison_task(s_dir, s_task, p_src, p_dest) { + + // resolve lex file + let p_lex_file = glob.sync(p_src+'/*.jisonlex')[0]; + + // register new task + gulp.task(s_task, [this.task('transpile')], () => { + + // load jison source file + return gulp.src(p_src+'/*.jison') + + // handle uncaught exceptions thrown by any of the plugins that follow + .pipe($.plumber()) + + // do not recompile unchanged files + .pipe($.cached(s_task)) + + // compile jison script + .pipe(through.obj(function(d_file, s_encoding, f_done) { + + // no file + if(d_file.isNull()) return f_done(null, d_file); + + // stream + if(d_file.isStream()) return f_done(new $.util.PluginError('gulp-jison-parser', 'Streams not supported')); + + // try generating parser + try { + // ref file contents + let s_contents = d_file.contents.toString(); + + // parse jison grammer + let h_grammar = ebnf_parser.parse(s_contents); + + // read lex file + let s_lex_contents = fs.readFileSync(p_lex_file, 'utf-8'); + + // set lex option on grammar + h_grammar.lex = lex_parser.parse(s_lex_contents); + + // pass grammer to jison to generate parser + let s_parser = new Generator(h_grammar, {}).generate(); + + // convert parser string to buffer + d_file.contents = new Buffer(s_parser); + + // rename file extension + d_file.path = $.util.replaceExtension(d_file.path, '.js'); + + // add this file to stream + this.push(d_file); + } + catch (err) { + // Convert the keys so PluginError can read them + err.lineNumber = err.line; err.fileName = err.filename; + + // Add a better error message + err.message = `${err.message} in file ${err.fileName} line no. ${err.lineNumber}`; + + // throw error + throw new $.util.PluginError('jison', err); + } + + // done + return f_done(); + })) + + // rename output file + .pipe($.rename('parser.js')) + + // write output to dist directory + .pipe(gulp.dest(p_dest)); + }); + }; +}; diff --git a/gulp/load-tasks.js b/gulp/load-tasks.js new file mode 100644 index 0000000..55fe5dd --- /dev/null +++ b/gulp/load-tasks.js @@ -0,0 +1,145 @@ + +import fs from 'fs'; +import path from 'path'; +import util from 'util'; + +const S_THIS_FILE = path.basename(__filename); + +export default function(gulp, $, config) { + + // prep tasks hash + let h_tasks = {}; + + // prep hash of task lists + let h_task_lists = {}; + + // + let a_dependencies = []; + + // + const mk_task = function(s_task_spec, s_dir) { + let [s_task_type, ...a_args] = s_task_spec.split(/:| +/g); + let f_task_maker = h_tasks[s_task_type]; + + // no such task type + if(!f_task_maker) { + throw `no such task type "${s_task_type}" found in gulp directory`; + } + + // + if('string' === typeof s_task_type) { + // create task name + let s_task = `${s_task_type}-${s_dir}`; + + // create src and dest paths + let p_src = path.join(config.src, s_dir); + let p_dest = path.join(config.dest, s_dir); + + // forward task details to task maker + f_task_maker.apply({ + + // pass task-spec args + args: a_args, + + // enable maker to indicate dependencies + task(s_mutate) { + // make other task name + let s_other_task = `${s_mutate}-${s_dir}`; + + // push to list + a_dependencies.push({ + spec: s_mutate, + dir: s_dir, + name: s_other_task, + }); + + // return other task name + return s_other_task; + }, + }, [s_dir, s_task, p_src, p_dest]); + + // ref corresponding task list + let a_task_list = h_task_lists[s_task_type]; + + // corresponding task list does not yet exist; create it + if(!a_task_list) { + a_task_list = h_task_lists[s_task_type] = []; + } + + // append task name to its corresponding task list + a_task_list.push(s_task); + } + }; + + return { + load(h_groups) { + + // fetch js task files + fs.readdirSync(__dirname).filter((s_file) => { + return '.js' === path.extname(s_file) && S_THIS_FILE !== s_file; + }).forEach((s_file) => { + + // ref task type + let s_task_type = path.basename(s_file, '.js'); + + // load maker function into tasks hash + h_tasks[s_task_type] = require(`./${s_file}`).default(gulp, $, config); + }); + + // + Object.keys(config.targets).forEach((s_dir) => { + let a_groups = config.targets[s_dir]; + if('string' === typeof a_groups) a_groups = [a_groups]; + a_groups.forEach((s_group) => { + + // each task type + h_groups[s_group].forEach((s_task_spec) => { + mk_task(s_task_spec, s_dir); + }); + }); + }); + + // resolve all dependencies + while(a_dependencies.length) { + + // ref dependency + let h_dependency = a_dependencies.shift(); + + // dependency not yet exists + if(!gulp.tasks[h_dependency.name]) { + + // make that task + mk_task(h_dependency.spec, h_dependency.dir); + } + } + + // build default tasks for each type + for(let s_general_task in h_task_lists) { + let a_deps = h_task_lists[s_general_task]; + + // link dependencies to trigger those tasks + gulp.task(s_general_task, a_deps); + } + + // auto-add directory name aliases + Object.keys(config.targets).forEach((s_dir) => { + let a_targets = config.targets[s_dir]; + if('string' === typeof a_targets) a_targets = [a_targets]; + let a_task_types = h_groups[a_targets[0]]; + + // no task name conflict + if(!gulp.tasks[s_dir]) { + gulp.task(s_dir, [`${a_task_types[0]}-${s_dir}`]); + } + }); + + // add aliases + Object.keys(config.aliases || {}).forEach((s_alias) => { + let s_task = config.aliases[s_alias]; + + // register alias task + gulp.task(s_alias, [s_task]); + }); + }, + }; +} diff --git a/gulp/sublime.js b/gulp/sublime.js new file mode 100644 index 0000000..fee2831 --- /dev/null +++ b/gulp/sublime.js @@ -0,0 +1,27 @@ +import util from 'util'; + +// module +export default (gulp, $, config={}) => { + + // task maker + return function sublime(s_dir, s_task, p_src, p_dest) { + + // generate syntax highlighter + gulp.task(s_task, [this.task('tm-language')], () => { + + // path to sublime user packages remapper + let f_user_packages = config.sublime_user_packages; + if(f_user_packages) { + + // fetch path of user packages + let p_user_packages = f_user_packages(s_dir, s_task, p_src); + + // load textmate language files + return gulp.src(p_dest+'/**/*') + + // copy to sublime user plugins directory + .pipe(gulp.dest(p_user_packages)); + } + }); + }; +}; diff --git a/gulp/test.js b/gulp/test.js new file mode 100644 index 0000000..fa7d035 --- /dev/null +++ b/gulp/test.js @@ -0,0 +1,33 @@ + +import {Instrumenter} from 'isparta'; + +export default (gulp, $) => { + + return function(s_dir, s_task, p_src, p_dest) { + + // make pre task name + let s_pre_task = this.task('pre'); + + // pre-test + gulp.task(s_pre_task, () => { + return gulp.src(p_src+'/**/*.js') + .pipe($.istanbul({ + includeUntested: true, + instrumenter: Instrumenter + })) + .pipe($.istanbul.hookRequire()); + }); + + // test + gulp.task(s_task, [s_pre_task]); + + // // coveralls + // gulp.task(`coveralls-${s_task}`, [s_task], () => { + // if (!process.env.CI) { + // return; + // } + // return gulp.src(path.join(__dirname, 'coverage/lcov.info')) + // .pipe($.coveralls()); + // }); + }; +}; diff --git a/gulp/tm-language.js b/gulp/tm-language.js new file mode 100644 index 0000000..9bcc359 --- /dev/null +++ b/gulp/tm-language.js @@ -0,0 +1,60 @@ + +import through from 'through2'; +import plist from 'plist'; + +// module +export default (gulp, $) => { + + // task maker + return function tm_language(s_dir, s_task, p_src, p_dest) { + + // generate syntax highlighter + gulp.task(s_task, [this.task('clean'), this.task('tm-preferences')], () => { + + // load yaml text-mate language definition + return gulp.src(p_src+'/*.YAML-tmLanguage') + + // convert yaml => json + .pipe($.yaml()) + + // process plist => xml + .pipe(through.obj(function(d_file, s_encoding, f_done) { + + // prepare buffer for new contents + let d_contents; + + // empty file + if(d_file.isNull()) { + return f_done(null, d_file); + } + // buffer + else if(d_file.isBuffer()) { + // convert buffer to string, parse JSON, build plist + let s_plist = plist.build(JSON.parse(d_file.contents.toString())); + + // convert string back to buffer + d_contents = new Buffer(s_plist, s_encoding); + } + // stream + else if(d_file.isStream()) { + this.emit('error', new $.util.PluginError('plist', 'Stream not supported')); + return f_done(); + } + + // set file contents + d_file.contents = d_contents; + + // all done + return f_done(null, d_file); + })) + + // rename file extension => .tmLanguage + .pipe($.rename((h_path) => { + h_path.extname = '.tmLanguage'; + })) + + // write to dist diretory + .pipe(gulp.dest(p_dest)); + }); + }; +}; diff --git a/gulp/tm-preferences.js b/gulp/tm-preferences.js new file mode 100644 index 0000000..5ce66fe --- /dev/null +++ b/gulp/tm-preferences.js @@ -0,0 +1,16 @@ + +// module +export default (gulp, $) => { + + // task maker + return function(s_dir, s_task, p_src, p_dest) { + + // register task + gulp.task(s_task, () => { + + // simply copy tmPreferences to dist + return gulp.src(p_src+'/*.tmPreferences') + .pipe(gulp.dest(p_dest)); + }); + }; +}; diff --git a/gulp/transpile.js b/gulp/transpile.js new file mode 100644 index 0000000..5a0df72 --- /dev/null +++ b/gulp/transpile.js @@ -0,0 +1,35 @@ + +// module +export default (gulp, $) => { + + // make transpile task + return function(s_dir, s_task, p_src, p_dest) { + + // register new task + gulp.task(s_task, () => { + + // load all javascript source files + return gulp.src(p_src+'/*.js') + + // handle uncaught exceptions thrown by any of the plugins that follow + .pipe($.plumber()) + + // do not recompile unchanged files + .pipe($.cached(s_task)) + + // lint all javascript source files + .pipe($.eslint()) + .pipe($.eslint.format()) + + // preserve mappings to source files for debugging + .pipe($.sourcemaps.init()) + + // transpile + .pipe($.babel()) + .pipe($.sourcemaps.write()) + + // write output to dist directory + .pipe(gulp.dest(p_dest)); + }); + }; +}; diff --git a/gulpfile.babel.js b/gulpfile.babel.js new file mode 100644 index 0000000..1989fe8 --- /dev/null +++ b/gulpfile.babel.js @@ -0,0 +1,39 @@ + +// native imports +import path from 'path'; + +// gulp +import gulp from 'gulp'; + +// load gulp plugins +import plugins from 'gulp-load-plugins'; +const $ = plugins({ + pattern: ['gulp-*', 'vinyl-*'], // load gulp and vinyl modules + replaceString: /^(?:gulp|vinyl)(-|\.)/, +}); + +// gulp module-level config +import config from './config.module.js'; + +// gulp user-level config +let user_config = {}; +try { user_config = require('./config.user.js').default; } catch(e) {} + +// gulp task makers +import tasks from './gulp/load-tasks'; + +// specify how config targets map to tasks +tasks(gulp, $, Object.assign({}, user_config, config)).load({ + + // transpiling javascript source to dist + js: ['transpile', 'develop:transpile'], + + // compiling language compiler from jison + language: ['jison', 'develop:jison'], + + // generate tm text highlighting files + 'syntax-highlighting': ['tm-language', 'sublime', 'ace-mode', 'develop:sublime'], +}); + +// // default +// gulp.task('default', ['build']); diff --git a/lib/compiler/assembler.js b/lib/compiler/assembler.js new file mode 100644 index 0000000..8d1d04e --- /dev/null +++ b/lib/compiler/assembler.js @@ -0,0 +1,215 @@ + +const H_MANDATORY_PREFIXES = { + volt: 'http://volt-name.space/ontology/', + vs: 'http://volt-name.space/ontology/subject', + vp: 'http://volt-name.space/ontology/predicate', + vo: 'http://volt-name.space/ontology/object', + this: 'http://volt-name.space/ontology/This', + input: 'http://volt-name.space/vocab/input#', + output: 'http://volt-name.space/vocab/output#', + rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + xsd: 'http://www.w3.org/2001/XMLSchema#', + owl: 'http://www.w3.org/2002/07/owl#', +}; + +const program = { + + prefixes(h_statement) { + // ref prefixes + let h_prefixes = this.prefixes; + + // each prefix line + h_statement.prefixes.forEach((h_line) => { + + // grab prefix + let s_prefix = h_line.prefix.slice(0, -1); + + // ref expansion + let p_expansion = h_line.iri.slice(1, -1); + + // prefix already defined + if(h_prefixes[s_prefix]) { + // prefixes are not identical + if(p_expansion !== h_prefixes[s_prefix]) { + this.error(`cannot redefine prefix '${h_line.prefix}:'`); + } + } + else { + h_prefixes[s_prefix] = p_expansion; + } + }); + }, + + constants(h_statement) { + // ref program constants hash + let h_constants = this.constants; + + // each constant + h_statement.constants.forEach((h_constant) => { + + // ref constant name + let s_name = h_constant.name; + + // constant already defined + if(h_constants[s_name]) { + this.error(`constant already defined: ${s_name} = "${h_constants[s_name]}"; refusing to change value to "${h_constant.value}"`); + } + else { + h_constants[s_name] = h_constant.value; + } + }); + }, + + injector(h_injector) { + this.injectors[h_injector.name] = h_injector; + }, + + relation(h_relation) { + this.body.push(h_relation); + }, + + + + // // loads trigger statements into program + // trigger(h_program, h_trigger) { + + // // + // h_program.triggers.push(h_trigger); + // }, + + + // // loads properties into program + // property(h_program, h_entity) { + + // // ref body statements + // let a_body = h_entity.body; + + // // extract top-declarative statements + // for(let i=a_body.length-1; i>=0; i--) { + + // // ref statement + // let h_statement = a_body[i]; + + // // ref statement type + // let s_type = h_statement.type; + + // // match top-declarative statements + // switch(s_type) { + // case 'abstract': + // case 'using': + // s_type += '_fields'; + // case 'version': + + // // remove from body + // a_body.splice(i, 1); + + // // move to entity + // h_entity[s_type].unshift(h_statement); + // break; + // } + // } + + // // using block exists but entity does not extend anything + // if(h_entity.using_fields.length && !h_entity.extends) { + // h_program.warn(`${h_entity.type} ${h_entity.iri} has a 'using' block but does not extend anything`); + // } + + // // + // h_program.body.push(h_entity); + // }, + + // // loads properties block into program + // properties(h_program, h_properties) { + + // // ref modifiers + // let a_modifiers = h_properties.modifiers; + + // // + // let a_trigger_properties = []; + // let s_trigger_iri = ''; + + // // + // h_properties.properties.forEach((h_property) => { + + // // + // let s_extends; + + // // copy attributes from the properties block onto each individual + // a_modifiers.forEach((h_modifier) => { + + // // push extending modifier onto each property + // if('extending' === h_modifier.type) { + // s_extends = h_modifier.iri; + // } + // // add trigger for this property + // else if('triggered_via' === h_modifier.type) { + // a_trigger_properties.push(h_property.iri); + // s_trigger_iri = h_modifier.iri; + // } + // }); + + // // load the property normally + // program.property(h_program, { + // type: 'property', + // iri: h_property.iri, + // extends: s_extends || '', + // version: [], + // abstract_fields: [], + // using_fields: [], + // body: h_property.body, + // }); + // }); + + // // there was a trigger + // if(s_trigger_iri) { + + // // add trigger + // program.trigger(h_program, { + // type: 'trigger', + // iri: s_trigger_iri, + // properties: a_trigger_properties, + // }); + // } + // }, + + // // loads methods into program + // method(h_program, h_entity) { + + // // + // h_program.body.push(h_entity); + // }, +}; + +export default (a_declarations) => { + + // create program structure + let h_program = { + + // lookups + prefixes: Object.assign({}, H_MANDATORY_PREFIXES), + constants: {}, + injectors: {}, + + // lists + triggers: [], + body: [], + + // debugging + warnings: [], + errors: [], + warn(s_msg) { + this.warnings.push(s_msg); + }, + error(s_msg) { + this.errors.push(s_msg); + }, + }; + + // process each declaration + a_declarations.forEach((h_declaration) => { + program[h_declaration.type].apply(h_program, [h_declaration]); + }); + + // + return h_program; +}; diff --git a/lib/compiler/ast.js b/lib/compiler/ast.js new file mode 100644 index 0000000..d6f8fcf --- /dev/null +++ b/lib/compiler/ast.js @@ -0,0 +1,364 @@ +import util from 'util'; +import arginfo from 'arginfo'; + +// +import assembler from './assembler'; + + +// +const H_MATH_OPS = { + '+': (a, b) => a + b, + '-': (a, b) => a - b, + '*': (a, b) => a * b, + '/': (a, b) => a / b, +}; + +// +let h_immediate_constants = {}; + +module.exports = { + + inspect(...a_args) { + debugger; + let [z_check] = a_args; + }, + + /** + * helpers: + **/ + + push(a_list, z_item) { + a_list.push(z_item); + return a_list; + }, + + append(a_appendage, a_primary) { + console.warn(typeof a_primary+': '+a_primary); + return a_primary.concat(a_appendage); + }, + + merge(h_a, h_b) { + for(let s_key in h_a) { + h_b[s_key] = h_a[s_key]; + } + return h_b; + }, + + option(h_pass, z_if, s_key) { + if(z_if) h_pass[s_key] = true; + return h_pass; + }, + + + /** + * main types: + **/ + + Prefixes: (a_prefixes) => ({ + type: 'prefixes', + prefixes: a_prefixes, + }), + + Constants(a_constants) { + + a_constants.forEach((h_constant) => { + h_immediate_constants[h_constant.name] = h_constant.gets; + }); + + console.log(a_constants); + + return { + type: 'constants', + constants: a_constants, + }; + }, + + Injector: (s_name, a_params, h_body) => ({ + type: 'injector', + name: s_name, + params: a_params, + body: h_body, + }), + + Relation: (p_iri, h_body, h_modifiers, p_extends) => ({ + type: 'relation', + iri: p_iri, + modifiers: h_modifiers, + extends: p_extends, + body: h_body, + }), + + + /** + * modifiers + **/ + + Modifiers: (h_modifiers) => { + return { + modifiers: h_modifiers, + }; + }, + + + /** + * prefixes body + **/ + + Prefix: (s_prefix, p_iri) => ({ + prefix: s_prefix, + iri: p_iri, + }), + + + /** + * constants body + **/ + + ConstantAssignment: (s_name, h_value) => ({ + name: s_name, + gets: h_value, + }), + + + /** + * relation body + **/ + + Yield: (h_expression) => ({ + type: 'yield', + expression: h_expression, + }), + + Return: (h_expression) => ({ + type: 'return', + expression: h_expression, + }), + + + /** + * parameters + **/ + + InjectorParameter: (a_modifiers, s_parameter) => ({ + type: 'injector_parameter', + modifiers: a_modifiers, + parameter: s_parameter, + }), + + /** + * abstract destruct + **/ + + AbstractDestruct: (s_variable, a_assignments) => ({ + type: 'abstract_destruct', + variable: s_variable, + assignments: a_assignments, + }), + + AbstractDestructAssignment: (s_variable, h_filter, h_iri) => ({ + variable: s_variable, + iri: h_iri, + }), + + + /** + * assignment statement + **/ + + Assignment: (h_variable, s_operator, h_expression) => ({ + type: 'assignment', + variable: h_variable, + operator: s_operator, + expression: h_expression, + }), + + VariableDestruct: (s_text, a_labels) => { + let s_name = s_text.replace(/:\[$/, ''); + return { + type: 'variable_destruct', + name: s_name, + labels: a_labels, + ids: a_labels.map(s_label => s_name+':'+s_label), + }; + }, + + Variable: (s_name) => ({ + type: 'variable', + name: s_name, + id: s_name, + }), + + VariableAt: (s_text) => { + let [s_name, s_at] = s_text.split(/:/); + return { + type: 'variable_at', + name: s_name, + at: s_at, + id: s_text, + }; + }, + + + /** + * if ... else + **/ + + If: (h_expression, h_then, h_else) => ({ + type: 'if', + expression: h_expression, + then: h_then, + else: h_else, + }), + + /** + * implicit select + **/ + + ImplicitSelect: (s_entity, h_variable, h_expression) => ({ + type: 'implicit_select', + entity: s_entity, + variable: h_variable, + expression: h_expression, + }), + + ImplicitPropertyExpression(h_lhs, h_operation) { + if(!h_operation) return h_lhs; + return { + type: 'property_expression', + lhs: h_lhs, + operator: h_operation.operator, + rhs: h_operation.rhs, + }; + }, + + ImplicitPropertyOperation: (s_operator, h_rhs) => ({ + operator: s_operator, + rhs: h_rhs, + }), + + Iri: (p_iri) => ({ + type: 'iri', + iri: p_iri, + }), + + JoinerCall: (s_joiner, a_args) => ({ + type: 'joiner_call', + args: a_args, + }), + + + /** + * general select + **/ + + SelectVariable: (h_variable, a_filters) => ({ + type: 'select_variable', + variable: h_variable, + filters: a_filters, + }), + + FilterIs: (s_is) => ({ + type: 'filter_is', + is: s_is, + }), + + FilterDatatype: (p_iri) => ({ + type: 'filter_datatype', + datatype: p_iri, + }), + + FilterConstant: (s_constant) => ({ + type: 'filter_constant', + constant: s_constant, + }), + + /** + * expression + **/ + + Expression(h_lhs, h_operation) { + // no rhs + if(!h_operation) return h_lhs; + + // ref rhs + let h_rhs = h_operation.rhs; + + // ref operator + let s_operator = h_operation.operator; + + // operation is statically evaluatable + if(H_MATH_OPS[s_operator]) { + + // lhs is constant + if('constant' === h_lhs.type) { + + // replace with constant + h_lhs = h_immediate_constants[h_lhs.value]; + } + + // rhs is constant + if('constant' === h_rhs.type) { + + // replace with constant + h_rhs = h_immediate_constants[h_rhs.value]; + } + + // both types are numeric + if('number' === h_lhs.type && 'number' === h_rhs.type) { + + // execute operation + return { + type: 'number', + value: H_MATH_OPS[s_operator](h_lhs.value, h_rhs.value), + }; + } + } + + // build normal lhs/rhs + return { + type: 'operation', + operator: s_operator, + lhs: h_lhs, + rhs: h_rhs, + }; + }, + + ExpressionOperation: (s_operator, h_expression) => ({ + operator: s_operator, + rhs: h_expression, + }), + + Boolean: (b_value) => ({ + type: 'boolean', + value: b_value, + }), + + Constant: (s_name) => ({ + type: 'constant', + name: s_name, + }), + + Number: (s_value) => ({ + type: 'number', + value: parseFloat(s_value), + }), + + FunctionCall: (p_iri, a_args) => ({ + type: 'function_call', + iri: p_iri, + args: a_args, + }), + + + InterpolatedString: (a_parts) => ({ + type: 'interpolated_string', + parts: a_parts, + }), + + + /** + * program + **/ + + Program: (a_declarations) => assembler(a_declarations), +}; diff --git a/lib/compiler/compiler.js b/lib/compiler/compiler.js new file mode 100644 index 0000000..be2e140 --- /dev/null +++ b/lib/compiler/compiler.js @@ -0,0 +1,449 @@ + +// libraries +import classer from 'classer'; +import clone from 'clone'; +// import rapunzel from 'rapunzel'; + +// local classes +import parser from './parser'; +import router from './router'; +import optimizer from './optimizer'; +import serializer from './serializer'; +import rapunzel from './rapunzel'; + +// +const _private = Symbol(); + +/** +* defaults: +**/ + +const H_DEFAULT_MAKE_PROCEDURE_CONTEXT = { + needs_resolve: {}, +}; + + +// convert a label to camel case +const camel_case = (s_label) => { + + // split label into words + let a_words = s_label.split(/[^\w]/g); + + // capitalize first letter of each word + return a_words.map(s => s[0].toUpperCase()+s.substr(1)).join(''); +}; + + +const rdf = { + object_list: (a) => (a.object_list = 1, a), + + string: (h) => `"${h.value.replace(/"/g, '\\"')}"^^xsd:string`, + variable: (h) => `"${h.id}"^^volt:Variable`, + operator: (s) => `"${s}"^^volt:Operator`, + + expression(h_expression) { + debugger; + return h_expression; + }, + + term: router('rdf.term', 'type', { + iri: (h) => h.iri, + number: (h) => h.value, + string: (h) => rdf.string(h), + variable: (h) => rdf.variable(h), + constant: (h, h_program) => rdf.term(h_program.constants[h], h_program), + operation: (h, h_program) => ({ + a: 'volt:Operation', + 'volt:operator': rdf.operator(h.operator), + 'volt:lhs': rdf.term(h.lhs, h_program), + 'volt:rhs': rdf.term(h.rhs, h_program), + }), + function_call: (h, h_program) => ({ + a: 'volt:FunctionCall', + 'volt:function': h.iri, + 'volt:arguments': h.args.map(h_a => rdf.term(h_a, h_program)), + }), + }), + + this: router('rdf.this', { + subject: () => 'this:Subject', + object: () => 'this:Object', + }), +}; + + +// +const unparallelizable = router('unparallelizable', 'type', { + + // this statement a function call, must occur by itself + function_call: () => true, + + // variable + variable: (h, h_vars) => (h_vars[h.id] = 1, false), + variable_at: (h, h_vars) => (h_vars[h.id] = 1, false), + + // static data + number: () => false, + string: () => false, + + // recurse on left and right terms + operation: (h, h_vars) => unparallelizable(h.lhs, h_vars) || unparallelizable(h.rhs, h_vars), +}); + + +// +const destruct = router('destruct', 'type', { + + select_variable: (h) => ({ + filters: h.filters || [], + variable: rdf.variable(h.variable), + }), +}); + +// +const graph_pattern = { + + implicit_select: router('graph_pattern', 'expression.type', { + + iri: (h, s_variable) => [{ + a: 'volt:Triple', + 'vs:': rdf.this(h.entity), + 'vp:': h.expression.iri, + 'vo:': s_variable, + }], + }), +}; + +const sparql = { + + query: router('sparql', 'type', { + + implicit_select(h_select, h_program, h_query) { + + // + let { + filters: a_filters, + variable: s_variable, + } = destruct(h_select.variable); + + // this subject/object needs to be resolved + if(h_program.context.needs_resolve[h_select.entity]) { + // select this __ + h_query.selections.push(rdf.this(h_select.entity)); + + // mark as resolved + h_program.context.needs_resolve[h_select.entity] = false; + } + + // select variable + h_query.selections.push(s_variable); + + // add filters to query + h_query.filters.push(...a_filters); + + // + let a_patterns = graph_pattern.implicit_select(h_select, s_variable, h_program, h_query); + + // + h_query.bgp.push(...a_patterns); + }, + }), +}; + + +// +const build = { + + select_query(a_items, h_program) { + + let h_query = { + selections: [], + filters: [], + bgp: [], + }; + + // accumulate each select query fragment + a_items.map((h_item) => { + sparql.query(h_item, h_program, h_query); + }); + + // create primary basic graph pattern using triples + let a_where = [{ + a: 'volt:BasicGraphPattern', + 'volt:triples': h_query.bgp, + }]; + + // add each top-level filter to where + h_query.filters.forEach((h_filter) => { + a_where.push({ + a: 'volt:Filter', + 'volt:expression': rdf.term(h_filter, h_program), + }); + }); + + // anti-reflexive + if(h_program.context.anti_reflexive) { + // add filter to bgp + a_where.push({ + a: 'volt:Filter', + 'volt:expression': rdf.term({ + type: 'operation', + operator: '!=', + lhs: { + type: 'iri', + iri: rdf.this('subject'), + }, + rhs: { + type: 'iri', + iri: rdf.this('object'), + }, + }), + }); + + // remove anti-reflexive flag + h_program.context.anti_reflexive = false; + } + + // bundle into structure fragment + return { + a: 'volt:SelectQuery', + 'volt:select': h_query.selections, + 'volt:where': a_where, + }; + }, +}; + + +// +const make = { + + nested(s_rdf_type, f_builder) { + // make operator + return (w_item, ...a_args) => ( + Object.assign({ + a: `volt:${camel_case(s_rdf_type)}`, + }, f_builder(w_item, ...a_args)) + ); + }, + + body(a_body, h_program) { + // create new optimizer context + let h_context = optimizer.context(h_program); + + debugger; + + // each statement in body + a_body.forEach((h_statement) => { + // route statement + let w_result = volt.statement(h_statement, h_program, h_context); + + // statement returned structure directly, it did not use optimizer + if(w_result) { + // close previous, and then append this result + h_context.close().push(w_result); + } + }); + + // return structure + return h_context.close(); + }, + + procedure(h_context={}) { + // make operator + return (h_procedure, h_program) => { + // copy-set context on program hash + h_program = Object.assign({}, + h_program, + { + context: Object.assign({}, + H_DEFAULT_MAKE_PROCEDURE_CONTEXT, + clone(h_context), + h_procedure.modifiers + ), + } + ); + + // render procedure structure + return { + a: `volt:${camel_case(h_procedure.type)}`, + 'volt:name': h_procedure.iri, + 'volt:instructions': make.body(h_procedure.body, h_program), + }; + }; + }, +}; + + + +// +const volt = router.group('volt', 'type', { + + declaration: { + + relation: make.procedure({ + anti_reflexive: true, + needs_resolve: { + subject: true, + object: true, + }, + }), + }, + + statement: { + + assignment: optimizer('assignment', { + + test(h_assignment, a_items) { + // prep hash of variables this expression uses + let h_vars = {}; + + // this term's expression is unparallelizable; do not merge this assignment + if(unparallelizable(h_assignment.expression, h_vars)) return false; + + // if any of the previous statements contain dependent variables, do not merge this assignment + return !a_items.some((h_item) => { + // ref variable from item + let h_variable = h_item.variable; + + // single variable assignment + if(h_variable.id) { + // lookup if some variable in current expression depends on this variable found in previous expressions + return h_vars[h_variable.id]; + } + else { + // lookup if one of the variable:ats from previous expression.. + return h_variable.ids.some((s_id) => { + // is used in current expression + return h_vars[s_id]; + }); + } + }); + }, + + build: make.nested('assignment', (a_items, h_program) => ({ + 'volt:assign': rdf.object_list( + a_items.map((h_item) => ({ + 'volt:variable': rdf.variable(h_item.variable), + 'volt:operator': rdf.operator(h_item.operator), + 'volt:expression': rdf.term(h_item.expression, h_program), + })) + ), + })), + }), + + if: make.nested('if-then-else', (h_if, h_program) => { + let h = { + 'volt:if': rdf.term(h_if.expression, h_program), + 'volt:then': make.body(h_if.then, h_program), + }; + if(h.else) { + h['volt:else'] = make.body(Array.isArray(h_if.else)? h_if.else: [h_if.else], h_program); + } + return h; + }), + + + implicit_select: optimizer('select-query', { + + test: (h_item, a_items) => true, + + build: build.select_query, + }), + + yield: make.nested('yield', (h_yield, h_program) => ({ + 'volt:expression': rdf.term(h_yield.expression), + })), + }, +}); + + + +// +class builder { + constructor(h_program) { + Object.assign(this, { + [_private]: h_program + }); + } + + prologue(add) { + + // add each prefix item + let h_prefixes = this[_private].prefixes; + for(let s_prefix in h_prefixes) { + add(`@prefix ${s_prefix}: <${h_prefixes[s_prefix]}>`); + } + + // add blankline after prefix block + add.blank(); + } +} + + +// +class model_builder extends builder { + + static build_sequence() { + return ['prologue', 'body']; + } + + constructor(h_program) { + super(h_program); + } + + body(add) { + + // each body statement + this[_private].body.forEach((h_declaration) => { + + // annotation comment + add(`# ${ + Object.keys(h_declaration.modifiers).map(s => s+' ').join('') + }${h_declaration.type} ${h_declaration.iri}`, {close: ''}); + + // route declaration + serializer( + volt.declaration(h_declaration, this), add + ); + }); + } +} + + +/** +* main: +**/ +const local = classer('compiler', (h_config) => { + + // destruct config + let { + code: s_program, + } = h_config; + + // prep program hash + let h_program; + + // parse program + try { + h_program = parser.parse(s_program); + } + catch(e_parse) { + return { + error: e_parse, + }; + } + + // build model ttl + let s_model = rapunzel({ + builder: new model_builder(h_program), + config: { + closing_delimiter: ' .', + }, + }); + + return { + model: s_model, + }; +}); + +export default local; diff --git a/lib/compiler/optimizer.js b/lib/compiler/optimizer.js new file mode 100644 index 0000000..6e27ece --- /dev/null +++ b/lib/compiler/optimizer.js @@ -0,0 +1,69 @@ + +/** +* class: +**/ +export default Object.assign((s_group_id, h_structure) => { + + // wrapper + return (h_item, h_program, h_context) => { + + // ref current context's group id + let s_context_group_id = h_context.group_id; + + // item is of different group + if(s_group_id !== s_context_group_id) { + + // there are items in the queue + if(h_context.items.length) { + // build & close previous list + h_context.close(); + } + + // update context group id + h_context.group_id = s_group_id; + + // update context builder + h_context.build = h_structure.build; + } + + // ref item list + let a_items = h_context.items; + + // call structure's test function to tell whether or not this item should be merged with previous + if(a_items.length && !h_structure.test(h_item, a_items, h_program)) { + + // close & build previous + h_context.close(); + } + + // add this item, whether merging or starting new group + a_items.push(h_item); + }; +}, { + + /** + * public static: + **/ + + context: (h_program) => ({ + items: [], + output: [], + + // build last item group + close() { + + // there are items to build + if(this.items.length) { + // build previous context, append to output + this.output.push( + this.build(this.items, h_program) + ); + + // reset item list + this.items.length = 0; + } + + return this.output; + }, + }), +}); diff --git a/lib/compiler/rapunzel.js b/lib/compiler/rapunzel.js new file mode 100644 index 0000000..d3fdf9d --- /dev/null +++ b/lib/compiler/rapunzel.js @@ -0,0 +1,122 @@ + +import classer from 'classer'; + +// default config for adder +let H_DEFAULT_ADDER_CONFIG = { + closing_delimiter: '', + indent_char: '\t', +}; + + +// adder +const adder = (a_chunks, h_config=H_DEFAULT_ADDER_CONFIG) => { + + // destruct config + let { + closing_delimiter: s_closing_delimiter, + indent_char: s_indent_char, + } = h_config; + + // level of indentation + let c_indent_count = 0; + + // filo queue of closing delimiters + let a_closing_delimiters = [s_closing_delimiter]; + + + // operator + let add = classer.operator((s_chunk, h_add_options={}) => { + + // append close delimiter + let s_close = h_add_options.hasOwnProperty('close') + ? (h_add_options.close || '') + : s_closing_delimiter; + + // merge with previous using delimiter + if(h_add_options.hasOwnProperty('merge') && a_chunks.length) { + debugger; + a_chunks.push(''); + } + // new chunk + else { + a_chunks.push( + s_indent_char.repeat(c_indent_count)+s_chunk+s_close + ); + } + }, { + + // auto-indenting, delimiter-injecting block opener + open(s_opening, s_delimiter, h_add_options={}) { + + // open block + add(s_opening || '', + // merge options, do not close previous line + Object.assign(h_add_options, {close: false})); + + // set closing delimiter + a_closing_delimiters.push(s_delimiter); + s_closing_delimiter = s_delimiter; + + // increase indentation + c_indent_count += 1; + }, + + // close block and decrease indentation + close(s_closing, h_add_options={}) { + + // decrease indentation + c_indent_count -= 1; + + // pop a closing delimiter of end of list + a_closing_delimiters.pop(); + + // fetch next + s_closing_delimiter = a_closing_delimiters.slice(-1)[0]; + + // close block + add(s_closing || '', h_add_options); + }, + + // adds blankline(s) + blank(n_newlines=1) { + while(0 < n_newlines--) a_chunks.push(''); + }, + }); + + return add; +}; + + + +export default (h_args) => { + + // destruct config + let { + builder: k_builder, + config: h_config, + } = h_args; + + // ref build sequence + let a_build_sequence = k_builder.constructor.build_sequence(); + + // prep string chunks + let a_chunks = []; + + // make a new adder + let k_adder = adder(a_chunks, + Object.assign({ + closing_delimiter: ' ;', + indent_char: ' ', + }, h_config) + ); + + // each sequence + a_build_sequence.forEach((s_sequence_name) => { + + // call sequence builder + k_builder[s_sequence_name](k_adder); + }); + + // combine all string chunks + return a_chunks.join('\n'); +}; diff --git a/lib/compiler/router.js b/lib/compiler/router.js new file mode 100644 index 0000000..fe34fe0 --- /dev/null +++ b/lib/compiler/router.js @@ -0,0 +1,104 @@ + +import classer from 'classer'; + +/** +* class: +**/ +const local = classer('router', (s_name, z_router, h_locals) => { + + // shuffle args + if(!h_locals) { + h_locals = z_router; + z_router = undefined; + } + + // + return (...a_args) => { + + // prep route name & default return value + let s_route; + let w_return; + + // ref router type + let s_router_type = typeof z_router; + + // router is string + if('string' === s_router_type) { + + // split by decimal + let a_properties = z_router.split(/\./g); + + // no need for recursion + if(1 === a_properties.length) { + // use that property name of 0th arg + s_route = a_args[0][a_properties[0]]; + } + // recurse + else { + // start with 0th arg + let w_node = a_args[0]; + while(a_properties.length) { + // shift property from each word + w_node = w_node[a_properties.shift()]; + } + + // end at terminus + s_route = w_node; + } + } + // router is function + else if('function' === s_router_type) { + // prep route callback; set route value + let f_set_route = (_s_route) => s_route = _s_route; + + // find which route to use + w_return = z_router.apply(h_locals, [f_set_route, ...a_args]); + } + // router is void + else if('undefined' === s_router_type) { + // stringify arg value + s_route = a_args[0]+''; + } + + // route was set + if(s_route) { + // ref handler + let k_route_handler = h_locals[s_route]; + + // route does not exist + if(!k_route_handler) { + local.fail(`${s_name} cannot route "${s_route}"`); + } + + // forward call to route handler + return k_route_handler(...a_args); + } + // route not set; explicit return value + else { + return w_return; + } + }; +}, { + +/** +* public static: +**/ + + // define how to route from an input to the local handlers + group(s_group_name, w_router, h_handlers) { + let h_output = {}; + + // each handler + for(let s_handler_name in h_handlers) { + let h_handler = h_handlers[s_handler_name]; + + // create handler interface + h_output[s_handler_name] = local(`${s_group_name}.${s_handler_name}`, w_router, h_handler); + } + + // render callee as output + return h_output; + }, +}); + +export default local; diff --git a/lib/compiler/serializer.js b/lib/compiler/serializer.js new file mode 100644 index 0000000..875e678 --- /dev/null +++ b/lib/compiler/serializer.js @@ -0,0 +1,50 @@ + +export default function local(z_block, add, s_predicate='') { + + // ref block type + let s_block_type = typeof z_block; + + // predicate was given, format it properly + if(s_predicate) s_predicate += ' '; + + // direct serialization + if('string' === s_block_type || 'number' === s_block_type) { + add(s_predicate+z_block); + } + // a list of some type + else if(Array.isArray(z_block)) { + // object list + if(z_block.object_list) { + // each item in list + z_block.forEach((w_item) => { + local(w_item, add, s_predicate); + }); + } + // rdf:list + else { + // open rdf list + add.open(s_predicate+'(', ''); + + // each item in list + z_block.forEach((w_item) => { + local(w_item, add); + }); + + // close rdf list + add.close(')'); + } + } + // blanknode + else if('object' === typeof z_block) { + // open blanknode + add.open(s_predicate+'[', ' ;'); + + // each key in blanknode object + for(let s_key in z_block) { + local(z_block[s_key], add, s_key); + } + + // close blanknode + add.close(']'); + } +} diff --git a/lib/compiler/volt.jison b/lib/compiler/volt.jison new file mode 100644 index 0000000..88ecf8b --- /dev/null +++ b/lib/compiler/volt.jison @@ -0,0 +1,364 @@ +/* ------------------------ */ +/* abstract syntax tree functions */ +/* ------------------------ */ +%{ + + var ast = require('./ast.js'); + + // copy all abstract syntax tree constructors onto global scope object + for(var s_thing in ast) { + global[s_thing] = ast[s_thing]; + } +%} + + +/* ------------------------ */ +/* operators & precedence */ +/* ------------------------ */ + +%left '>' '<' '>=' '<=' '!=' '==' +%left '+' '-' +%left '*' '/' +%left '^' + + +%start grammar + +/* enable EBNF grammar syntax */ +%ebnf + +%% +/* ------------------------ */ +/* language grammar */ +/* ------------------------ */ + +grammar + : declaration* EOF + { return Program($1) } + ; + +declaration + : prefixes + | constants + | injector + | function + | relation + ; + + +/* ------------------------ */ +/* main types */ +/* ------------------------ */ + +prefixes + : PREFIXES prefixes_body -> Prefixes($prefixes_body) + ; + +constants + : CONSTANTS constants_body -> Constants($2) + ; + +injector + : INJECTOR INLINE injector_parameters '{' injector_body '}' -> Injector($INLINE, $injector_parameters, $injector_body) + ; + +function + : FUNCTION + ; + +relation + : relation_modifiers RELATION iri (extends)? relation_body -> Relation($iri, $relation_body, $relation_modifiers, $4) + ; + + +/* ------------------------ */ +/* modifiers +/* ------------------------ */ + +extends + : EXTENDS iri -> $iri + ; + +relation_modifiers + : ABSTRACT? -> Modifiers({abstract: $1}) + ; + + + +/* ------------------------ */ +/* prefixes body +/* ------------------------ */ +prefixes_body + : prefix_statement -> [$1] + | '{' prefix_statement* '}' -> $2 + ; + +prefix_statement + : PREFIXED_NAME IRIREF ';' -> Prefix($PREFIXED_NAME, $IRIREF) + ; + + +/* ------------------------ */ +/* constants body +/* ------------------------ */ +constants_body + : '{' constant_assignment* '}' -> $2 + ; + +constant_assignment + : CONSTANT '=' assignment_value ';' -> ConstantAssignment($CONSTANT, $assignment_value) + ; + +assignment_value + : expression + ; + + +/* ------------------------ */ +/* injector body +/* ------------------------ */ +injector_body + : injector_statement* + ; + +injector_statement + : RETURN expression ';' -> Return($expression) + ; + + +/* ------------------------ */ +/* relation body +/* ------------------------ */ +relation_body + : '{' relation_statement* '}' -> $2 + ; + +relation_statement + : assignment + | abstract_destruct + | relation_if + | implicit_select + | YIELD expression ';' -> Yield($expression) + ; + + +/* ------------------------ */ +/* parameters +/* ------------------------ */ +parameters + : '(' (VARIABLE ',')* VARIABLE? ')' -> push($2, $3) + ; + +injector_parameters + : '(' (injector_parameter ',')* injector_parameter ')' -> push($2, $3) + ; + +injector_parameter + : injector_parameter_modifier* VARIABLE -> InjectorParameter($1, $2) + ; + +injector_parameter_modifier + : SINGLE + ; + + +/* ------------------------ */ +/* statements +/* ------------------------ */ + +/* ------------------------ */ +/* abstract destruct +/* ------------------------ */ +abstract_destruct + : ABSTRACT VARIABLE '{' abstract_destruct_assignment* '}' -> AbstractDestruct($VARIABLE, $4) + ; + +abstract_destruct_assignment + : variable variable_filter? '=' iri ';' -> AbstractDestructAssignment($variable, $2, $iri) + ; + +/* ------------------------ */ +/* assignment statement +/* ------------------------ */ +assignment + : variable_gets assignment_operator expression ';' -> Assignment($1, $2, $3) + ; + +assignment_operator + : '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' + ; + +variable_gets + : variable + | VARIABLE_DESTRUCT WORD? word_more* ']' -> VariableDestruct($VARIABLE_DESTRUCT, push($WORD, $word_more)) + ; + +word_more + : ',' WORD? -> $WORD + ; + + +variable + : VARIABLE -> Variable($VARIABLE) + | VARIABLE_AT -> VariableAt($VARIABLE_AT) + ; + + +/* ------------------------ */ +/* if ... else +/* ------------------------ */ +relation_if + : IF expression relation_body relation_else? -> If($2, $3, $4) + ; + +relation_else + : ELSE (relation_if | relation_body) -> $2 + ; + +/* ------------------------ */ +/* implicit select +/* ------------------------ */ +implicit_select + : (SUBJECT | OBJECT)[entity] select_variable '=' implicit_property_expression ';' + -> ImplicitSelect($entity, $select_variable, $implicit_property_expression) + ; + +implicit_property_expression + : implicit_property_value implicit_property_operation? + -> ImplicitPropertyExpression($1, $2) + /*-> inspect($1, $2)*/ + ; + +implicit_property_operation + : implicit_property_operator implicit_property_expression -> ImplicitPropertyOperation($1, $2) + ; + +implicit_property_operator + : UNION -> 'union' + | ','-> 'else' + ; + +implicit_property_value + : iri -> Iri($iri) + | INLINE '(' (implicit_property_value ',')* implicit_property_value ')' -> JoinerCall($INLINE, push($3, $4)) + ; + + +/* ------------------------ */ +/* general select +/* ------------------------ */ +select_variable + : variable variable_filter? -> SelectVariable($variable, $2) + ; + +variable_filter + : '(' (filter ',')* filter ')' -> push($2, $3) + ; + +filter + : filter_is -> FilterIs($filter_is) + | '^^' iri -> FilterDatatype($iri) + | CONSTANT -> FilterConstant($CONSTANT) + ; + +filter_is + : 'IRI' | 'NUMERIC' | 'LITERAL' | 'STRING' | 'BLANKNODE' + ; + +filter_more + : ',' filter -> $filter + ; + + + +/* ------------------------ */ +/* expression +/* ------------------------ */ +expression + : expression_value expression_operation? -> Expression($1, $2) + ; + +expression_operation + : expression_operator expression -> ExpressionOperation($1, $2) + ; + +expression_value + : common_value + | '(' expression ')' -> $expression + ; + +expression_operator + : '+' | '-' | '*' | '/' | '&&' | '||' | '==' | '!=' | '>' | '<' | '>=' | '<=' + ; + +expression_iri + : iri function_call_args? -> $2? FunctionCall($1, $2): Iri($iri) + ; + +function_call_args + : '(' (expression ',')* expression? ')' -> push($2, $3) + ; + + + +/* ------------------------ */ +/* return +/* ------------------------ */ + + + +/* ------------------------ */ +/* common values +/* ------------------------ */ + +common_value + : variable + | interpolated_string + | expression_iri + | constant + | number + | boolean + ; + +constant: CONSTANT -> Constant($1) + ; + +number + : NUMBER -> Number($1) + | PI -> Number(Math.PI) + | TAU -> Number(Math.PI * 2) + ; + +boolean + : TRUE -> Boolean(true) + | FALSE -> Boolean(false) + ; + +interpolated_string + : '`' interpolated_string_part* '`' -> InterpolatedString($2) + ; + +interpolated_string_part + : STRING + | '${' interpolated_string_expression '}' -> $2 + ; + +interpolated_string_expression + : variable + | 'FUNCTION_CALL(' (interpolated_string_expression ',')* interpolated_string_expression? ')' + ; + + + +/**** TERMINAL PROXIES ****/ + + +iri + : IRIREF + | PREFIXED_NAME + ; + +quoted_string + : SINGLE_QUOTED_STRING -> $1.slice(1, -1) + | DOUBLE_QUOTED_STRING -> $1.slice(1, -1) + ; diff --git a/lib/compiler/volt.jisonlex b/lib/compiler/volt.jisonlex new file mode 100644 index 0000000..d9652a2 --- /dev/null +++ b/lib/compiler/volt.jisonlex @@ -0,0 +1,227 @@ + +/* ------------------------ */ +/* regular expressions */ +/* ------------------------ */ + +numeric [+-]?((\d+(\.\d*)?)|(\.\d+)) +alphanum_u [A-Za-z_0-9] +word {alphanum_u}+ + +prefix [A-Za-z](({alphanum_u}|[.\-])*{alphanum_u})? +postfix {alphanum_u}|[:]|%[A-Fa-f0-9]{2}|\\[_~.\-!$&'()*+,;=/?#@%] + +variable [?]{word} +constant [$]{word} +inline [&]{word} + +regex [~][/](?:[^/\n\\]|\\.)+[/][a-z]* + +iriref [<]([^<>"{}|^` \t\n\\\u0000-\u0020])+[>] + + +ident [?][A-Za-z_\u00c0-\u024f][A-Za-z_0-9\$\u00c0-\u024f]* + +pname {prefix}?[:]({postfix}({postfix}|".")*{postfix}?)? + +single_quoted_string ['](?:[^'\\]|\\.)*['] +double_quoted_string ["](?:[^"\\]|\\.)*["] + + +%s block destruct backtick interpolate + +%options flex + +%% +/* ------------------------ */ +/* lexical vocabulary */ +/* ------------------------ */ + + + +\s*\n+\s* {} /* return '\n' */ +"}" this.popState(); return '}' + +{word} return 'WORD' +"," return ',' +"]" this.popState(); return ']' +. return 'INVALID' + +"${" this.pushState('interpolate'); return '${'; +"`" this.popState(); return '`' +"\\`" return 'STRING' +"$" return 'STRING' +[^`$]* return 'STRING' + +"}" this.popState(); return '}' +{variable} return 'VARIABLE' +{variable}":"{word} return 'VARIABLE_AT' +{word}"(" return 'FUNCTION_CALL(' +")" return ')' +"," return ',' +\s+ { /*ignore ws*/ } +. return 'INVALID' + +\s+ { /* skip whitespace */ } +// \n+ { /* skip newlines */ } + +[#][^\n]*\n { /* ignore line comments */ } +[#][^\n]*$ { return 'EOF'; } +"/*"[^]*?"*/" { /* ignore block comments */ } + +/* ------------------------ */ +/* top-level statement keywords */ +/* ------------------------ */ +/*"prefix" return 'PREFIX' +"constant" return 'CONSTANT'*/ + + +/* ------------------------ */ +/* top-level cluster keywords */ +/* ------------------------ */ + +"prefixes" return 'PREFIXES' +"constants" return 'CONSTANTS' + + +/* ------------------------ */ +/* type modifiers */ +/* ------------------------ */ + +"abstract" return 'ABSTRACT' +/*"transitive" return 'TRANSITIVE'*/ + + +/* ------------------------ */ +/* type keywords */ +/* ------------------------ */ + +"injector" return 'INJECTOR' +"function" return 'FUNCTION' +"relation" return 'RELATION' + + + +/*"alias" return 'ALIAS'*/ + + +/* ------------------------ */ +/* block relations */ +/* ------------------------ */ + +/*"extends" return 'EXTENDS'*/ +/*"extending" return 'EXTENDING'*/ + + +/* ------------------------ */ +/* mode triggers */ +/* ------------------------ */ + + +/* ------------------------ */ +/* body keywords */ +/* ------------------------ */ + +"super" return 'SUPER' + +"subject" return 'SUBJECT' +"object" return 'OBJECT' + +"if" return 'IF' +"else" return 'ELSE' + +"return" return 'RETURN' +"yield" return 'YIELD' + +/*"input" return 'INPUT'*/ +/*"output" return 'OUTPUT'*/ + + +/* ------------------------ */ +/* query keywords */ +/* ------------------------ */ + +"this" return 'THIS' + + +/* ------------------------ */ +/* query/expression types */ +/* ------------------------ */ + +{iriref} return 'IRIREF' +{pname} return 'PREFIXED_NAME' + +{variable} return 'VARIABLE' +{variable}":"{word} return 'VARIABLE_AT' +{variable}":[" this.begin('destruct'); return 'VARIABLE_DESTRUCT' + +{constant} return 'CONSTANT' +{inline} return 'INLINE' + +{single_quoted_string} return 'SINGLE_QUOTED_STRING' +{double_quoted_string} return 'DOUBLE_QUOTED_STRING' +"`" this.begin('backtick'); return '`' + +{regex} return 'REGULAR_EXPRESSION' + +{numeric} return 'NUMBER' + +"a" return 'A' +"@parent" return 'QUICK_PARENT' + +"single" return 'SINGLE' + +"{" return '{' +"}" return '}' +"(" return '(' +")" return ')' +"[" return '[' +"]" return ']' + +">=" return '>=' +"<=" return '<=' +"==" return '==' +"!=" return '!=' +"&&" return '&&' +"||" return '||' +">" return '>' +"<" return '<' + +"+=" return '+=' +"-=" return '-=' +"*=" return '*=' +"/=" return '/=' +"%=" return '%=' +"&=" return '&=' +"|=" return '|=' + +"=" return '=' +"," return ',' +";" return ';' +"." return '.' + +"+" return '+' +"-" return '-' +"*" return '*' +"/" return '/' + +"~" return '~' + +"&" return "&" +"u" return "UNION" + + +"pi" return 'PI' +"tau" return 'TAU' + +"true" return 'TRUE' +"false" return 'FALSE' + +"iri" return 'IRI' +"numeric" return 'NUMERIC' +"literal" return 'LITERAL' +"string" return 'STRING' +"blanknode" return 'BLANKNODE' + +{word} return 'WORD' + +<> return 'EOF' diff --git a/lib/syntax-lex/lex.YAML-tmLanguage b/lib/syntax-lex/lex.YAML-tmLanguage new file mode 100644 index 0000000..30e8552 --- /dev/null +++ b/lib/syntax-lex/lex.YAML-tmLanguage @@ -0,0 +1,207 @@ +# [PackageDev] target_format: plist, ext: tmLanguage +name: Lex/Flex +scopeName: source.lex +fileTypes: [l] +uuid: 92E842A0-9DE6-4D31-A6AC-1CDE0F9547C5 + +patterns: +- comment: first section of the file - definitions + name: meta.section.definitions.lex + begin: \A(?!%%$) + end: ^(?=%%$) + patterns: + - include: '#includes' + - name: comment.block.c.lex + begin: /\* + end: \*/|$ + - name: meta.definition.lex + begin: ^(?i)([a-z_][a-z0-9_-]*)(?=\s|$) + beginCaptures: + '1': {name: entity.name.function.lex} + end: $ + patterns: + - include: '#regexp' + - name: meta.start-condition.lex + begin: ^(%[sx])(?=\s|$) + beginCaptures: + '1': {name: punctuation.definition.start-condition.lex} + end: $ + patterns: + - match: (?i)[a-z_][a-z0-9_-]* + - name: invalid.illegal.lex + match: \S + - name: meta.options.lex + begin: ^(%option)\s(?=\S) + beginCaptures: + '1': {name: keyword.other.option.lex} + end: $ + patterns: + - name: support.other.option.lex + match: \b(?:(?:no)?(?:[78]bit|align|backup|batch|c\+\+|debug|default|ecs|fast|full|interactive|lex-compat|meta-ecs|perf-report|read|stdout|verbose|warn|array|pointer|input|unput|yy_(?:(?:push|pop|top)_state|scan_(?:buffer|bytes|string))|main|stack|stdinit|yylineno|yywrap)|(?:case(?:ful|less)|case-(?:in)?sensitive|(?:always|never)-interactive))\b + - name: keyword.other.option.lex + begin: ^%(?:array|pointer) + end: $ + patterns: + - name: invalid.illegal.lex + match: \S + +- begin: ^(%%)$ + beginCaptures: + '1': {name: punctuation.separator.sections.lex} + end: \Z.\A(?# never end) + patterns: + - comment: second section of the file - rules + name: meta.section.rules.lex + begin: ^(?!%%$) + end: ^(?=%%$) + patterns: + - name: meta.rule.lex + begin: ^(?!$) + end: $ + patterns: + - include: '#includes' + - comment: rule pattern + begin: (?i)^(<(?:(?:[a-z_][a-z0-9_-]*,)*[a-z_][a-z0-9_-]|\*)>)?(?:(<>)(\s*))?(?=\S) + beginCaptures: + '1': {name: keyword.other.start-condition.lex} + '2': {name: keyword.operator.eof.lex} + '3': {name: invalid.illegal.regexp.lex} + end: (?=\s)|$ + patterns: + - include: '#regexp' + - comment: 'TODO: %} should override embedded scopes' + begin: (%\{) + beginCaptures: + '1': {name: punctuation.definition.code.lex} + end: (%\})(.*) + endCaptures: + '1': {name: punctuation.terminator.code.lex} + '2': {name: invalid.illegal.ignored.lex} + patterns: + - include: '#csource' + - comment: 'TODO: eol should override embedded scopes' + name: meta.rule.action.lex + begin: (?=\S) + end: $ + patterns: + - include: '#csource' + - comment: third section of the file - user code + contentName: meta.section.user-code.lex + begin: ^(%%)$ + beginCaptures: + '1': {name: punctuation.separator.sections.lex} + end: \Z.\A(?# never end) + patterns: + - include: '#csource' + +repository: + csource: + patterns: + - name: support.function.c.lex + match: \b(?:ECHO|BEGIN|REJECT|YY_FLUSH_BUFFER|YY_BREAK|yy(?:more|less|unput|input|terminate|text|leng|restart|_(?:push|pop|top)_state|_(?:create|switch_to|flush|delete)_buffer|_scan_(?:string|bytes|buffer)|_set_(?:bol|interactive))(?=\(|$))\b + - include: source.c + + includes: + patterns: + - comment: 'TODO: $} should override the embedded scopes' + name: meta.embedded.source.c.lex + begin: ^%\{$ + end: ^%\}$ + patterns: + - include: source.c + - comment: 'TODO: eol should override the embedded scopes' + name: meta.embedded.source.c.lex + begin: ^[ \t]+ + end: $ + patterns: + - include: source.c + - begin: (\/\*) + end: (\*\/) + name: comment.block.documentation.lex + + re_escape: + name: constant.character.escape.lex + match: \\(?i:[0-9]{1,3}|x[0-9a-f]{1,2}|.) + + rec_csource: + begin: \{ + end: \} + patterns: + - include: source.c + - include: '#csource' + + regexp: + name: string.regexp.lex + begin: \G(?=\S)(\^)? + end: (\$)?(?:(?=\s)|$) + captures: + '1': {name: keyword.control.anchor.regexp.lex} + patterns: + - include: '#subregexp' + + subregexp: + patterns: + - include: '#re_escape' + - name: constant.other.character-class.set.lex + begin: (\[)(\^)?-? + beginCaptures: + '1': {name: punctuation.definition.character-class.set.lex} + '2': {name: keyword.operator.negation.regexp.lex} + end: -?(\]) + endCaptures: + '1': {name: punctuation.terminator.character-class.set.lex} + patterns: + - include: '#re_escape' + - name: constant.other.character-class.set.lex + match: \[:(?:(?:alnum|alpha|blank|cntrl|x?digit|graph|lower|print|punct|space|upper)|(.*?)):\] + captures: + '1': {name: invalid.illegal.regexp.lex} + - name: variable.other.lex + match: (?i){[a-z_][a-z0-9_-]*} + - name: keyword.operator.quantifier.regexp.lex + begin: \{ + end: \} + patterns: + - match: (?<=\{)[0-9]*(?:,[0-9]*)?(?=\}) + - comment: '{3} counts should only have digit[,digit]' + name: invalid.illegal.regexp.lex + match: '[^}]' + - name: string.quoted.double.regexp.lex + begin: '"' + end: '"' + patterns: + - include: '#re_escape' + - comment: make ** or +? or other combinations illegal + begin: ([*+?])(?=[*+?]) + beginCaptures: + '1': {name: keyword.operator.quantifier.regexp.lex} + end: (?=[^*+?]) + patterns: + - name: invalid.illegal.regexp.lex + match: . + - name: keyword.operator.quantifier.regexp.lex + match: '[*+?]' + - comment: <> is handled in the rule pattern + name: invalid.illegal.regexp.lex + match: <> + - name: meta.group.regexp.lex + begin: (\() + beginCaptures: + '1': {name: punctuation.definition.group.regexp.lex} + end: (\))|(?=\s)|$(?#end on whitespace because regex does) + endCaptures: + '1': {name: punctuation.terminator.group.regexp.lex} + patterns: + - name: invalid.illegal.regexp.lex + match: / + - include: '#subregexp' + - comment: detection of multiple trailing contexts + begin: (/) + beginCaptures: + '1': {name: keyword.operator.trailing-match.regexp.lex} + end: (?=\s)|$ + patterns: + - name: invalid.illegal.regexp.lex + match: /|\$(?!\S) + - include: '#subregexp' +keyEquivalent: ^~L diff --git a/lib/syntax-lex/lex.tmPreferences b/lib/syntax-lex/lex.tmPreferences new file mode 100644 index 0000000..1310d81 --- /dev/null +++ b/lib/syntax-lex/lex.tmPreferences @@ -0,0 +1,34 @@ + + + + + name + Comments + scope + source.lex + settings + + shellVariables + + + name + TM_COMMENT_START_2 + value + /* + + + name + TM_COMMENT_END_2 + value + */ + + + name + TM_COMMENT_DISABLE_INDENT_2 + value + yes + + + + + diff --git a/lib/syntax-volt/volt.YAML-tmLanguage b/lib/syntax-volt/volt.YAML-tmLanguage new file mode 100644 index 0000000..d43b6a9 --- /dev/null +++ b/lib/syntax-volt/volt.YAML-tmLanguage @@ -0,0 +1,691 @@ +# [PackageDev] target_format: plist, ext: tmLanguage +--- +name: Volt +scopeName: source.volt +fileTypes: ["volt"] +uuid: 458bd326-4790-4e74-a88e-2d13ccaf8722 + +patterns: + +- include: '#comment' + +- include: '#constants' +- include: '#prefixes' + +- include: '#injector' +- include: '#function' +- include: '#relation' +- include: '#method' + + +repository: + + comment: + patterns: + - begin: (\/\*) + end: (\*\/) + name: comment.block.volt + - begin: (#) + end: (\n|$) + name: comment.line.volt + + + +## +# atoms: +## + + string: + patterns: + - begin: (') + end: (') + name: string.quoted.single.volt + patterns: + - name: invalid.string.newline + match: \n + - name: constant.character.escape.volt + match: \\. + - begin: (") + end: (") + name: string.quoted.double.volt + patterns: + - name: invalid.string.newline + match: \n + - name: constant.character.escape.volt + match: \\. + + interpolated-string: + patterns: + - begin: (`) + end: (`) + # name: string.interpolated.volt + patterns: + - begin: (\$\{) + beginCaptures: + '1': {name: markup.raw.volt} + end: (\}) + endCaptures: + '1': {name: markup.raw.volt} + patterns: + - include: '#expression' + - match: . + name: string.interpolated.volt + + constant: + patterns: + - match: (\$\w+) + name: constant.language.volt + + variable: + patterns: + - match: (\?\w+)(:\w+)? + captures: + '1': {name: variable.parameter.volt} + '2': {name: string.unquoted.volt} + + regex: + patterns: + - match: (~[/]([^/\\]|\\.)+[/])[a-z]* + name: string.regexp.volt + + argument-list: + patterns: + - begin: (\() + end: (\)) + patterns: + - match: (single) + name: storage.modifier.volt + - match: (\?\w+) + name: variable.parameter.volt + + datatype: + patterns: + - match: (\^\^\w*:\w*) + name: storage.type.volt + + rdf-type: + patterns: + - match: (a)\s+(\w*:\w*) + captures: + '1': {name: keyword.control.volt} + '2': {name: string.unquoted.volt} + + expression: + patterns: + - include: '#variable' + - include: '#regex' + - include: '#string' + - include: '#interpolated-string' + - match: (subject|predicate|object) + name: constant.language.volt + - match: (match(es)?|has) + name: keyword.operator.volt + - match: (null) + name: storage.type.volt + - match: \b(no|yes)\s+(\?\w+) + captures: + '1': {name: constant.character.eval.volt} + '2': {name: entity.name.function.volt} + - match: (\w*:\w*)(?=\() + captures: + '1': {name: entity.name.function.volt} + - match: (\w*:\w*) + name: string.unquoted.predicate.volt + - match: ([<>!=]=) + name: constant.character.eval-math-operator.volt + - match: ([-+*/><]|&&|\|\|) + name: constant.character.eval-math-operator.volt + - match: ([+-]?((\d+(\.\d*)?)|(\.\d+))) + name: constant.numeric.volt + - match: (true|false|tau|pi) + name: constant.language.volt + - match: ([$]\w+) + name: constant.language.volt + - match: (sqrt|abs|uri|iri|concat|str(after|before|len|dt|lang|uuid|starts|ends)?) + name: support.function.volt + - match: (sum) + name: support.function.volt + - match: (,) + name: punctuation.separator.sequence.volt + - begin: (\() + end: (\)) + patterns: + - include: '#expression' + - match: ([^\s]+) + name: invalid.illegal.volt + + +## +# elements: +## + + prefix-definition: + patterns: + - match: (\w*:\w*) + name: constant.language.prefixed-name.volt + - begin: (<) + end: (>)\s*(?=;) + name: string.unquoted.uri.volt + + + # ?variable:property(filter) + variable-filter: + patterns: + - include: '#variable' + - begin: (\() #filter + end: (\)) + patterns: + - match: (or) + name: keyword.operator.volt + - include: '#datatype' + - include: '#rdf-type' + - include: '#constant' + - include: '#regex' + - match: (\w*:\w*) + name: entity.name.function.volt + - match: (iri|numeric|literal|string|blanknode|list) + name: storage.type.volt + - begin: (:\[) # destruct + end: (\]) + patterns: + - match: (\w*) + name: string.unquoted.volt + - match: (,|\s+) + name: markup.raw.volt + - match: . + name: invalid.illegal.volt + + # = #expression + gets-expression: + patterns: + - begin: ([%*/+-]?=) + beginCaptures: + '1': {name: keyword.operator.assignment.volt} + end: (;) + patterns: + - include: '#expression' + + implicit-property-expression: + patterns: + - match: (\w*:\w*) + name: string.unquoted.predicate.volt + - match: (\s)(u|U)\s # union operator + name: constant.character.volt + - begin: (?=\&\w+\() + end: (\)) + patterns: + - match: (&\w+) + name: entity.name.function.volt + - match: (\w*:\w*) + name: string.unquoted.predicate.volt + + implicit-property-extraction: + patterns: + - begin: (=) + end: (?=;) + patterns: + - include: '#implicit-property-expression' + + pattern: + patterns: + - include: '#sparql-pattern' + - include: '#contextual-pattern' + + sparql-pattern: + patterns: + - begin: (\{) + end: (\})([^\n]*) # (?:\s+(order\s+by\s+(?:(?:asc|desc)\(\)))?)) + endCaptures: + '1': {name: string.unquoted.sparql.volt} + '2': {name: string.unquoted.sparql.volt} + name: string.unquoted.sparql.volt + patterns: + - include: '#graph-pattern' + + contextual-pattern: + patterns: + - begin: (\[) + end: (\]) + name: string.unquoted.context-block.volt + patterns: + - include: '#graph-pattern' + + graph-pattern: + patterns: + - match: ((this|subject|property|object)|\W[@$]\w+) + name: constant.language.volt + - begin: (\[) + end: (\]) + patterns: + - include: '#graph-pattern' + - begin: (\{) + end: (\}) + patterns: + - include: '#graph-pattern' + - include: '#variable' + - match: (a) + name: string.interpolated.volt + - match: (\w*:\w*) + name: string.interpolated.volt + - match: (filter|not|exists|graph|select|where|limit|sum|group|order|by|group_concat|offset|if|coalesce|in|is(iri|blank|literal|numeric)|str|lang|datatype|iri|uri|str|bnode|strdt|strlang|uuid|struuid|strlen|regex|replace|substr|ucase|lcase|strstarts|strends|contains|strbefore|strafter|encode_for_uri|concat|langmatches|abs|round|ceil|floor|rand|now|year|month|day|hours|minutes|seconds|timezone|tz|md5|sha1|sha256|sha384|sha512|values|distinct|reduced|having|count|avg|min|max|sample) + name: keyword.operator.volt + + +## +# prologue: +## + + constants: + patterns: + - begin: \b(constants)\s*\{ + beginCaptures: + '1': {name: support.constant.other.constants.volt} + end: (\}) + patterns: + - match: ([$](\w+)) + name: constant.numeric.volt + - include: '#gets-expression' + + prefixes: + patterns: + - begin: (prefixes)\s*\{ + beginCaptures: + '1': {name: support.constant.other.prefixes.volt} + end: (\}) + patterns: + - include: '#prefix-definition' + - begin: (prefix)\s*(?=\w*:) + beginCaptures: + '1': {name: support.constant.other.prefix.volt} + end: (\n) + patterns: + - include: '#prefix-definition' + + + +## +# main signatures: +## + + injector: + patterns: + - begin: (injector)\s+(\&\w+)\s*(?=\([^\)]*\)\s*\{) + beginCaptures: + '1': {name: storage.type.volt} + '2': {name: entity.name.function.volt} + end: (\}) + patterns: + - include: '#argument-list' + - include: '#block-static-start' + + function: + patterns: + - begin: (function)\s+(\w*:\w*)\s*(?=\([^\)]*\)\s*\{) + beginCaptures: + '1': {name: storage.type.volt} + '2': {name: entity.name.function.volt} + end: (\}) + patterns: + - include: '#argument-list' + - include: '#block-start' + + relation: + patterns: + - begin: (?:(abstract)\s+)?(relation)\s+(\w*:\w*)(?:\s+(extends)\s+(\w*:\w*))?\s*\{ + beginCaptures: + '1': {name: storage.modifier.volt} + '2': {name: storage.type.volt} + '3': {name: entity.name.function.volt} + '4': {name: support.constant.extends.volt} + '5': {name: entity.name.class.super-class.volt} + end: (\}) + patterns: + - include: '#body' + + method: + patterns: + - begin: (?:(method)\s+(\w*:\w*))\s*\{ + beginCaptures: + '1': {name: storage.type.volt} + '2': {name: entity.name.function.volt} + end: (\}) + patterns: + - include: '#body-method' + +## +# main containers: +## + + body: + patterns: + - include: '#comment' + - include: '#abstract-destruct' + - include: '#assignment' + - include: '#if-else' + - include: '#select' + - include: '#implicit-select' + - include: '#return' + # - include: '#super' + # - include: '#field' + # - include: '#select' + # - include: '#group-destructure' + # - include: '#code' + # - include: '#target' + + body-method: + patterns: + - include: '#input' + - include: '#body' + + body-static: + patterns: + - include: '#comment' + - begin: (return) + beginCaptures: + '1': {name: keyword.operator.volt} + end: (;) + patterns: + - include: '#interpolated-string' + + +## +# body containers: +## + + block: + patterns: + - begin: (\{) + beginCaptures: + '1': {name: punctuation.other.volt} + end: (\}) + endCaptures: + '1': {name: punctuation.other.volt} + patterns: + - include: '#body' + + block-start: + patterns: + - begin: (\{) + beginCaptures: + '1': {name: punctuation.other.volt} + end: (?=\}) + endCaptures: + '1': {name: punctuation.other.volt} + patterns: + - include: '#body' + + block-static-start: + patterns: + - begin: (\{) + beginCaptures: + '1': {name: punctuation.other.volt} + end: (?=\}) + endCaptures: + '1': {name: punctuation.other.volt} + patterns: + - include: '#body-static' + + + +## +# body block openers: +## + + if-else: + patterns: + - begin: (if|else(?:\s+if)?) + beginCaptures: + '1': {name: keyword.operator.volt} + end: (?=\{) + patterns: + - include: '#expression' + - include: '#block' + + +## +# statements: +## + + abstract-destruct: + patterns: + - begin: (abstract)\s+(\?\w*) + beginCaptures: + '1': {name: keyword.operator.volt} + '2': {name: variable.parameter.volt} + end: (?=.) + patterns: + - include: '#block' + + assignment: + patterns: + - include: '#variable-filter' + - include: '#gets-expression' + + return: + patterns: + - begin: (yield|output|return)\s+ + beginCaptures: + '1': {name: keyword.operator.volt} + end: (;) + patterns: + - begin: (?=\{) + patterns: + - include: '#sparql-pattern' + end: (?=.) + - begin: (?=[^\{]) + patterns: + - include: '#expression' + end: (?=;) + # - include: '#sparql-pattern' + # - include: '#expression' + + implicit-select: + patterns: + - begin: (subject|object)\s+(?=\?) + beginCaptures: + '1': {name: keyword.operator.volt} + end: (?=\{|\[|\;) + patterns: + - include: '#variable-filter' + - include: '#implicit-property-extraction' + - include: '#graph-pattern' + + select: + patterns: + - begin: (select)\s+ + beginCaptures: + '1': {name: keyword.operator.volt} + end: (\n) + patterns: + - begin: (?=\?) + end: (?=\{) + patterns: + - include: '#select-variables' + - include: '#sparql-pattern' + + select-variables: + patterns: + - include: '#variable' + - match: (=) + name: keyword.operator.volt + - include: '#expression' + + input: + patterns: + - begin: (input)\s+(?=\?) + beginCaptures: + '1': {name: keyword.operator.volt} + end: (;) + patterns: + - include: '#variable-filter' + - match: (decluster)\s+(into)\s+ + name: keyword.operator.volt + + # super: + # patterns: + # - begin: (super)\s+(\?\w+)\s+\{ + # beginCaptures: + # '1': {name: keyword.operator.volt} + # '2': {name: variable.parameter.volt} + # # patterns: + # # - begin: (\?\w+)\s+\{ + # # beginCaptures: + # # '1': {name: variable.parameter.volt} + # patterns: + # - include: '#target' + # end: (\}) + + # body-argument: + # patterns: + # - begin: \b((?:optional\s+)?argument) + # beginCaptures: + # '1': {name: keyword.operator.volt} + # end: (\n) + # patterns: + # - include: '#field-variable' + # - include: '#field-datatype' + + # group-destructure: + # patterns: + # - begin: \b(abstract)\s+(\?\w+)\s*(\{) + # beginCaptures: + # '1': {name: keyword.operator.volt} + # '2': {name: variable.parameter.volt} + # patterns: + # - include: '#comment' + # - include: '#field-body' + # end: (\}) + + # field: + # patterns: + # - begin: \b(input|subject|predicate|object)\s+(?=\?) + # beginCaptures: + # '1': {name: keyword.operator.volt} + # end: (?=\n|\{|\[) + # patterns: + # - include: '#field-body' + # - include: '#pattern' + + # code: + # patterns: + # - begin: \b(using|evaluate)\s+(?=\?) + # beginCaptures: + # '1': {name: keyword.operator.stage.volt} + # end: (?=\n|,) + # patterns: + # - include: '#target' + # - include: '#code-block' + + # code-block: + # patterns: + # - begin: \b(using|evaluate)\s(\{) + # beginCaptures: + # '1': {name: keyword.operator.stage.volt} + # end: (\}) + # patterns: + # - include: '#target' + + + # output: + # patterns: + # - begin: \b(output)\s*(?=\{|\[) + # beginCaptures: + # '1': {name: keyword.operator.stage.volt} + # end: (?=\}|\]) + # patterns: + # - include: '#pattern' + + + +## field + + # field-body: + # patterns: + # - include: '#field-variable' + # - include: '#field-datatype' + # - include: '#field-assignment' + + # field-variable: + # patterns: + # - match: (\?\w+)(:\w+)? + # captures: + # '1': {name: variable.parameter.lhs-variable.volt} + # '2': {name: string.unquoted.volt} + # - begin: (\() + # end: (\)) + # patterns: + # - match: (\^\^\w*:\w*) + # name: storage.type.volt + # - match: (a)\s+(\w*:\w*) + # captures: + # '1': {name: keyword.control.volt} + # '2': {name: string.unquoted.volt} + # - match: (\$\w+) + # name: constant.language.volt + # - match: (\w*:\w*) + # name: entity.name.function.volt + # - include: '#regex' + + # field-datatype: + # patterns: + # - begin: \( + # end: \) + # patterns: + # - match: (\w*:\w*) + # name: support.type.datatype.volt + # - match: (iri|numeric|text) + # name: constant.language.volt + + # field-assignment: + # patterns: + # - begin: (=) + # beginCaptures: + # '1': {name: keyword.operator.assignment.volt} + # end: (;) + # patterns: + # - include: '#expression' + # - include: '#pattern' + + # pattern: + # patterns: + # - include: '#sparql-pattern' + # - include: '#context-pattern' + + # sparql-pattern: + # patterns: + # - begin: (\{) + # end: (\})([^\n]*) # (?:\s+(order\s+by\s+(?:(?:asc|desc)\(\)))?)) + # endCaptures: + # '1': {name: string.unquoted.sparql.volt} + # '2': {name: string.unquoted.sparql.volt} + # name: string.unquoted.sparql.volt + # patterns: + # - include: '#graph' + + # context-pattern: + # patterns: + # - begin: (\[) + # end: (\]) + # name: string.unquoted.context-block.volt + # patterns: + # - include: '#graph' + + # graph: + # patterns: + # - match: ((this|subject|property)|\W[@$]\w+) + # name: constant.language.volt + # - begin: (\[) + # end: (\]) + # patterns: + # - include: '#graph' + # - begin: (\{) + # end: (\}) + # patterns: + # - include: '#graph' + # - include: '#variable' + # - match: (a) + # name: string.interpolated.volt + # - match: (\w*:\w*) + # name: string.interpolated.volt + # - match: (filter|not|exists|graph|select|where|limit|sum|group|order|by|group_concat|offset|if|coalesce|in|is(iri|blank|literal|numeric)|str|lang|datatype|iri|uri|str|bnode|strdt|strlang|uuid|struuid|strlen|regex|replace|substr|ucase|lcase|strstarts|strends|contains|strbefore|strafter|encode_for_uri|concat|langmatches|abs|round|ceil|floor|rand|now|year|month|day|hours|minutes|seconds|timezone|tz|md5|sha1|sha256|sha384|sha512|values|distinct|reduced|having|count|avg|min|max|sample) + # name: keyword.operator.volt diff --git a/lib/syntax-volt/volt.tmPreferences b/lib/syntax-volt/volt.tmPreferences new file mode 100644 index 0000000..f16ab14 --- /dev/null +++ b/lib/syntax-volt/volt.tmPreferences @@ -0,0 +1,46 @@ + + + + + name + Comments + scope + source.volt + settings + + shellVariables + + + name + TM_COMMENT_START + value + # + + + name + TM_COMMENT_DISABLE_INDENT + value + yes + + + name + TM_COMMENT_START_2 + value + /* + + + name + TM_COMMENT_END_2 + value + */ + + + name + TM_COMMENT_DISABLE_INDENT_2 + value + yes + + + + + diff --git a/lib/volt/builder.js b/lib/volt/builder.js new file mode 100644 index 0000000..149d097 --- /dev/null +++ b/lib/volt/builder.js @@ -0,0 +1,784 @@ + +// libraries +import async from 'async'; +import classer from 'classer'; +import spaz from 'spaz'; + +// local classes +import evaluator from './evaluator'; + + +// open abstract connection to SPARQL endpoint +let $$ = spaz({ + engine: { + endpoint: 'http://localhost:3060/dbpedia', + http_methods: 'post', + }, + prefixes: { + graph: 'graph://', + volt: 'http://volt-name.space/ontology/', + input: 'http://volt-name.space/vocab/input#', + output: 'http://volt-name.space/vocab/output#', + stko: 'http://stko.geog.ucsb.edu/vocab/', + }, +}); + + +// +const A_JSON_DATATYPES = ['boolean', 'number', 'string']; + +// +const F_ROUTE_IS = (route, k) => route(k.$is()); +const F_ROUTE_TYPE = (route, k) => route(k.$type()); +const F_ROUTE_VALUE = (route, k) => route(k()); + + +// +const build_literal = (k_literal, h_context) => { + + // literal refers to a volt variable + if('Variable' === k_literal.$datatype()) { + // fetch compiled id of variable by name + return h_context.user_variable_id(k_literal()); + } + // literal has parseable datatype + else if(k_literal.$datatype.parseable()) { + let w_value = k_literal(); + + // javascript datatype of literal value + let s_js_datatype = typeof w_value; + + // literal can be safely converted to code via json + if(-1 !== A_JSON_DATATYPES.indexOf(s_js_datatype)) { + // stringify literal value + return JSON.stringify(w_value); + } + // literal needs more strict reconstruction + else { + // date-time + if(w_value instanceof Date) { + return `Date.parse(${JSON.stringify(w_value)})`; + } + // no idea + else { + local.fail(`no way to reconstruct ${s_js_datatype} literal in javascript: ${JSON.stringify(w_value)}`); + } + } + } + // literal is something else..? + else { + local.fail(`cannot reconstruct literal from datatype "${k_literal.$datatype()}"`); + } +}; + + +// +const router = (s_name, f_router, h_locals) => { + return classer.operator((...a_args) => { + + // prep route name + let s_route; + + // prep route callback; set route value + let f_set_route = (_s_route) => s_route = _s_route; + + // find which route to use + let w_return = f_router.apply(h_locals, [f_set_route, ...a_args]); + + // route was set + if(s_route) { + // ref handler + let k_route_handler = h_locals[s_route]; + + // route does not exist + if(!k_route_handler) { + local.fail(`${s_name} cannot route "${s_route}"`); + } + + // forward call to route handler + return k_route_handler(...a_args); + } + // route not set; explicit return value + else { + return w_return; + } + }); +}; + + +// define how to route from an input to the local handlers +const router_group = (s_group_name, f_router, h_handlers) => { + let h_output = {}; + + // each handler + for(let s_handler_name in h_handlers) { + let h_handler = h_handlers[s_handler_name]; + + // create handler interface + h_output[s_handler_name] = router(`${s_group_name}.${s_handler_name}`, f_router, h_handler); + } + + // render callee as output + return h_output; +}; + + +// +const sparql = { + + query: router('sparql.query', F_ROUTE_TYPE, { + + SelectQuery(k_query, h_context, add) { + // + let a_mapped_variables = []; + + // prep sparql query chain + add.body('sparql', 1); + + // map each select target in query to compiled parameter + let a_selects = k_query.select((k_select) => { + + // by selecting deserialized target + let z_select = sparql.select(k_select, h_context); + + // select created variable + if(z_select.map) { + // add to variable mapping + a_mapped_variables.push(z_select.map); + } + + // return selection as code + return z_select.code; + }); + + // add selections to query + add.body(`.select([${a_selects.join(',')}])`); + + // map each where target in query to compiled parameter + let a_wheres = k_query.where((k_where) => { + + // by adding deserialized target + return sparql.where(k_where, h_context, add); + }); + + // add query supplement + a_wheres.push(`this.query_supplement || ''`); + + // add where parts to query + add.body('.where(...[', 1); + + a_wheres.forEach((s_where) => { + add.body(`${s_where},`); + }); + + add.body('])', -1); + + // claim a variable from context + let s_vi_rows = h_context.next_script_variable('a_rows'); + let s_vi_row = h_context.next_script_variable('h_row'); + + // open asynchronous callback + add.async(`.dump().rows((${s_vi_rows}) => {`); + add.body(`if(!${s_vi_rows}.length) return yield_(false);`); + add.async(`each(${s_vi_rows}, (${s_vi_row}) => {`); + + // now that the query string has been made, destruct new select variables from results + let s_destruct_body = a_mapped_variables.map((h_variable_map) => { + return `'${h_variable_map.name}':${h_variable_map.get_id()}`; + }).join(', '); + + // destruct callback arg + add.body(`let {${s_destruct_body}} = ${s_vi_row};`); + + // create `self` script variable + add.body(`// locally-scoped script variable to house the triple resolved by the preceeding query`); + add.body(`let self = {subject: __subject || this.subject, predicate: this.predicate, object: __object || this.object};`); + }, + }), + + select: router('sparql.select', F_ROUTE_IS, { + + // variable + literal(k_literal, h_context) { + // select target literals can only be variable names + if('Variable' !== k_literal.$datatype()) { + local.fail(`select target literal does not have volt:Variable datatype: ${k_literal.$n3()}`); + } + + // ref variable name + let s_variable_name = k_literal(); + + // variable does not yet exist + if(!h_context.user_variable_exists(s_variable_name)) { + // create name extract + let h_variable_map = h_context.sparql_user_variable(s_variable_name); + + // select sparql variable + return { + map: h_variable_map, + code: `'${s_variable_name}'`, + }; + } + // variable already declared prior to query + else { + // use existing variable + return { + code: sparql.expression(k_literal, h_context), + }; + } + }, + + // reserved word + iri(k_iri, h_context) { + + // construct map/code via other routers + return { + map: sparql.reserved(k_iri, h_context), + code: sparql.expression(k_iri, h_context), + }; + }, + }), + + reserved: router('sparql.reserved', F_ROUTE_VALUE, { + + ThisSubject(k_iri, h_context) { + return h_context.sparql_user_variable('?_subject'); + }, + + ThisPredicate(k_iri, h_context) { + return h_context.sparql_user_variable('?_predicate'); + }, + + ThisObject(k_iri, h_context) { + return h_context.sparql_user_variable('?_object'); + }, + }), + + expression: router('sparql.expression', F_ROUTE_IS, { + + iri(k_iri, h_context) { + return sparql.iri(k_iri, h_context); + }, + + literal(k_literal, h_context) { + + // variable + if('Variable' === k_literal.$datatype()) { + // ref variable name + let s_variable_name = k_literal(); + + // variable exists in current context + if(h_context.user_variable_exists(s_variable_name)) { + // lookup its corresponding variable id + let s_variable_id = h_context.user_variable_id(s_variable_name); + + // flatten corresponding variable into n3 string + return `sparql.turtle(${s_variable_id})`; + } + // variable does not yet exist + else { + return `'${s_variable_name}'`; + } + } + // object intended to be simple literal + else if('Literal' === k_literal.$datatype()) { + return k_literal['@value']; + } + // typed literal + else { + return k_literal['@full']; + } + }, + + node() { + debugger; + }, + }), + + where: router('sparql.where', F_ROUTE_TYPE, { + + BasicGraphPattern(k_block, h_context) { + + let a_triples = k_block.triples((k_triple) => { + return sparql.triple(k_triple, h_context); + }); + + return `{type:'bgp', triples:[${a_triples.join(',')}]}`; + }, + + Filter(k_block, h_context) { + return `{type:'filter', expression:${sparql.filter(k_block.expression, h_context)}}`; + }, + }), + + triple: router('sparql.triple', F_ROUTE_TYPE, { + Triple(k_triple, h_context) { + // construct triple as n3 array + let a_triple = [ + sparql.expression(k_triple.subject, h_context), + sparql.expression(k_triple.predicate, h_context), + sparql.expression(k_triple.object, h_context), + ]; + + // return as serialized js array + return `[${a_triple.join(',')}]`; + }, + }), + + filter: router('sparql.filter', (route, k_expression, h_context) => { + + // expression is literal or iri + if(k_expression.$is.literal ||k_expression.$is.iri) { + // solver as entity + return sparql.expression(k_expression, h_context); + } + + // otherwise, route type + route(k_expression.$type()); + }, { + + Operation(k_expression, h_context) { + let a_join = [ + k_expression.lhs? sparql.expression(k_expression.lhs, h_context): '', + `'${k_expression.operator()}'`, + k_expression.rhs? sparql.expression(k_expression.rhs, h_context): '', + ]; + + return `[${a_join.join(',')}].join(' ')`; + }, + }), + + + iri: router('sparql.iri', function(route, k_iri) { + + // ref volt:-namespaced symbol + let s_symbol = k_iri(); + + // not a volt-namespaced reserved irir + if(!s_symbol) { + return `'<${k_iri['@id']}>'`; + } + + // special reserved symbol + route(s_symbol); + }, { + + ThisSubject(k_iri, h_context) { + return h_context.this_subject; + }, + + ThisPredicate(k_iri, h_context) { + return h_context.this_predicate; + }, + + ThisObject(k_iri, h_context) { + return h_context.this_object; + }, + }), +}; + + +// +const duties = router_group('duties', F_ROUTE_TYPE, { + + // + instruction: { + + Assignment(k_assignment, h_context, add) { + + // each assign + k_assignment('assign', (k_assign) => { + + // form expression + let h_expression = serialize.expression(k_assign.expression, h_context); + + // expression compiled asynchronous code + if(h_expression.async.length) { + h_expression.async.forEach((f_async) => { + f_async(add); + }); + } + + // ref compiled code from expression + let s_code = h_expression.code; + + // ref variable name + let s_variable_name = k_assign.variable(); + + // operator is not direct assignment + if(2 === k_assign.operator().length) { + let s_operator = k_assign.operator()[0]; + s_code = `x(${h_context.user_variable_id(s_variable_name)}, '${s_operator}', ${s_code})`; + } + + // assign value to variable + h_context.set_user_variable(s_variable_name, s_code, add); + }); + }, + + SelectQuery(k_query, h_context, add) { + + // forward to query builder + sparql.query(k_query, h_context, add); + }, + + IfThenElse(k_if_then_else, h_context, add) { + + // + let h_expression = serialize.expression(k_if_then_else.if, h_context); + + // synchronous + if(!h_expression.async.length) { + + // open if block + add.body(`if(as.boolean(${h_expression.code})) {`, 1); + + // add if body + k_if_then_else.then((k_then_instruction) => { + duties.instruction(k_then_instruction, h_context, add); + }); + + // close if block + add.body('}', -1); + + // there is an else + if(k_if_then_else.else) { + + // open else block + add.body(`else {`, 1); + + // add else body + k_if_then_else.else((k_else_instruction) => { + duties.instruction(k_else_instruction, h_context, add); + }); + + // close else block + add.body(`}`, -1); + } + } + else { + debugger; + } + }, + + Yield(k_yield, h_context, add) { + + let h_expression = serialize.expression(k_yield.expression, h_context); + + if(h_expression.async.length) { + debugger; + } + + let s_yield_result = h_context.next_script_variable('b_yield'); + + add.body(`let ${s_yield_result} = as.boolean(${h_expression.code});`); + add.body(`if(${s_yield_result}) {`, 1); + add.body(`return yield_(${s_yield_result}, self);`); + add.body('}', -1); + }, + + Debugger(k_debugger, h_context, add) { + add.body('debugger;'); + }, + }, +}); + + +// +const serialize = router_group('serialize', (route, k_entity, ...a_rest) => { + + // entity type + let s_entity_type = k_entity.$is(); + + // literal; serialize to javascript + if('literal' === s_entity_type) { + return { + async: [], + code: build_literal(k_entity, ...a_rest), + }; + } + // node + else if('node' === s_entity_type) { + route(k_entity.$type()); + } + // other + else { + local.fail(`cannot handle ${s_entity_type} types of entities`); + } +}, { + + // + expression: { + + Operation(k_operation, h_context) { + + // ref operator symbol + let s_operator = k_operation.operator(); + + // build lhs and rhs indepedently + let z_lhs = k_operation.lhs? serialize.expression(k_operation.lhs, h_context): 'null'; + let z_rhs = k_operation.rhs? serialize.expression(k_operation.rhs, h_context): 'null'; + + // + return { + async: [...z_lhs.async, ...z_rhs.async], + code: `x(${z_lhs.code}, '${s_operator}', ${z_rhs.code})`, + }; + }, + + FunctionCall(k_function_call, h_context) { + // prep list of asyncs + let a_asyncs = []; + + // compile argument expressions + let a_args = k_function_call.arguments((k_argument) => { + // ref expression + let h_expression = serialize.expression(k_argument, h_context); + + // expression has asynchronicity; push to asyncs list + if(h_expression.async.length) a_asyncs.push(...h_expression.async); + + // extract code from serializer + return h_expression.code; + }); + + // claim intermediate result variable + let s_intermediate = h_context.next_script_variable('w_i'); + + // return package + return { + async: [ + ...a_asyncs, + (add) => { + add.async(`call('${k_function_call.function['@id']}', [${a_args.join(',')}], (${s_intermediate}) => {`); + }, + ], + code: s_intermediate, + }; + }, + }, +}); + + +// +const context = () => { + // + let h_user_variables = {}; + let h_script_variables = {}; + + // + const mk_user_variable = (s_variable_name) => { + + // ref variable's id + let s_variable_id = h_user_variables[s_variable_name]; + + // variable not declared + if(!s_variable_id) { + + // create mapping from variable name to compiled alias + s_variable_id = h_user_variables[s_variable_name] = `_${s_variable_name.replace(/^[^\w\d]+/, '').replace(/[^\w\d]+/g, '_')}`; + + // new variable was created + return [s_variable_id, true]; + } + + // new variable was not created + return [s_variable_id, false]; + }; + + + // + return { + + // + this_subject: `'?_subject'`, + this_predicate: `'?_predicate'`, + this_object: `'?_object'`, + + // + next_script_variable(s_variable_name) { + + // no collisions + if(!h_script_variables.hasOwnProperty(s_variable_name)) { + // prevent future collisions + h_script_variables[s_variable_name] = 0; + + // return unclaimed variable name + return s_variable_name; + } + // would-be collisions + else { + // create collision-free variable name & advance auto-increment id + return `${s_variable_name}_${h_script_variables[s_variable_name]++}`; + } + }, + + // + user_variable_exists: (s_variable_name) => { + return h_user_variables.hasOwnProperty(s_variable_name); + }, + + // + user_variable_id: (s_variable_name) => { + + // variable map has variable + if(h_user_variables.hasOwnProperty(s_variable_name)) { + // + return h_user_variables[s_variable_name]; + } + // variable name not found in this map + else { + local.fail(`user variable "${s_variable_name}" does not exist in context`); + } + }, + + // sets a value to a variable + set_user_variable(s_variable_name, s_value, add) { + + // fetch variable's id + let [s_variable_id, b_created] = mk_user_variable(s_variable_name); + + // // variable is new + // if(b_created) { + // // add declaration to head + // add.head(`let ${s_variable_id};`); + // } + + // add assignment to sync body + add.body(`${b_created? 'let ': ''}${s_variable_id} = ${s_value};`); + }, + + // + sparql_user_variable(s_variable_name) { + // prep variable id + let s_variable_id; + + // + return { + name: s_variable_name.substr(1), + get_id() { + if(!s_variable_id) { + // fetch variable's id + let [_s_variable_id, b_created] = mk_user_variable(s_variable_name); + + // variable should be new, but it's not + if(!b_created) { + local.fail(`asked to create mapping for new user variable '${s_variable_name}' but variable already exists in current context`); + } + + // set variable id now + s_variable_id = _s_variable_id; + } + + // return if + return s_variable_id; + }, + toString() { + return s_variable_name.substr(1); + }, + }; + }, + }; +}; + + +// +const local = classer('builder', function(h_input) { + + let { + node: k_node, + } = h_input; + + // destruct nodes under target namespaces + let { + volt: k_node_volt, + } = k_node.$; + + // create new state for this property + let h_context = context(); + + // pretty print: indentation + let s_indent_chr = '\t'; + let s_indent = s_indent_chr.repeat(1); + + // + let s_head = ''; + let s_body = ''; + let s_tail = ''; + + // each instruction is evaluated in series + k_node_volt.instructions((k_instruction) => { + + // forward instruction to instruction builder + duties.instruction(k_instruction, h_context, { + head(s_add) { + s_head += s_indent_chr+s_add+'\n'; + }, + body(s_add, n_indent_add=0) { + // decrease indent before committing this string + if(n_indent_add < 0) s_indent = s_indent.slice(0, s_indent_chr.length*n_indent_add); + + // commit string + s_body += s_indent+s_add+'\n'; + + // increase indent after adding this string + if(n_indent_add > 0) s_indent += s_indent_chr.repeat(n_indent_add); + + }, + async(s_open, s_close='});') { + s_body += s_indent+s_open+'\n'; + s_tail = `${s_indent}${s_close}\n${s_tail}`; + s_indent += s_indent_chr; + }, + }); + }); + + // final async failure + s_body += `${s_indent}return yield_(false, self);\n`; + + // + let s_contents = s_head+s_body+s_tail; + + // + let h_args = { + util: evaluator({ + $$: { + // proxy spaz select call by choosing graphs too + select(...a_args) { + return $$.select(...a_args).from(['graph:source', 'graph:reference', 'graph:output']); + }, + }, + }), + }; + + // + let a_arg_names = Object.keys(h_args); + let a_arg_destructs = a_arg_names.map((s_arg_name) => { + // ref arg struct + let h_arg_struct = h_args[s_arg_name]; + + // build destruct assignment code + return `let {${ + Object.keys(h_arg_struct).join(', ') + }} = ${s_arg_name};`; + }); + + let s_whole = `(function(${a_arg_names.join(',')}) {\n` + +`${a_arg_destructs.map(s => s_indent_chr+s).join('\n')}\n${s_contents}\n})`; + + local.info(s_whole); + + ({ + code: s_whole + } = require('babel-core').transform(s_whole, { + presets: ['es2015'], + })); + + let f_property = eval(s_whole); + + // + return f_property; +}); + +// +export default local; diff --git a/lib/volt/engine.js b/lib/volt/engine.js new file mode 100644 index 0000000..765d0c4 --- /dev/null +++ b/lib/volt/engine.js @@ -0,0 +1,208 @@ +// native includes + +// third-party libraries +import async from 'async'; +import classer from 'classer'; +import graphy from 'graphy'; +import spaz from 'spaz'; + +// local classes +import relations from './relations'; +import plugins from './plugins'; + + + +/** +* defaults: +**/ + +// default graph groups +const H_GRAPH_GROUPS = { + content: ['graph:source', 'graph:reference', 'graph:output'], + model: ['graph:model', 'graph:reference'], + output: ['graph:output'], + fallacy: ['graph:fallacy'], + solution: ['graph:output', 'graph:fallacy'], +}; + +// which graphs are unique per each query +const A_UNIQUE_GRAPHS = [ + 'input', + 'cache', +]; + +// prefixes to load into spaz by default +const H_DEFAULT_PREFIXES = { + '': 'vocab://local/', + unit: 'http://qudt.org/vocab/unit#', + qudt: 'http://qudt.org/schema/qudt#', +}; + +// prefixes reserved and used by volt, mandatory for n3 queries +const H_REQUIRED_PREFIXES = { + graph: 'graph://', + volt: 'http://volt-name.space/ontology/', + input: 'http://volt-name.space/vocab/input#', + output: 'http://volt-name.space/vocab/output#', + stko: 'http://stko.geog.ucsb.edu/vocab/', +}; + + + +/** +* config: +**/ + +// +const N_ID_RADIX = 36; + + + +/** +* globals: +**/ + +// counter tracks unique id for queries +let i_query = 0; + + + +/** +* class: +**/ +const local = classer('engine', (h_config={}) => { + + // destruct config arg + let { + endpoint: s_endpoint, + plugins: h_plugins, + } = h_config; + + // open connection to SPARQL endpoint + let $$ = spaz({ + engine: { + endpoint: s_endpoint, + http_methods: 'post', + }, + prefixes: Object.assign({}, H_DEFAULT_PREFIXES, h_config.prefixes || {}, H_REQUIRED_PREFIXES), + }); + + // create plugin router + let k_plugin_router = plugins(h_config.plugins || {}, $$); + + + /** + * operator: + **/ + return classer.operator((s_sparql, f_okay) => { + + // new query id + let s_query_id = i_query.toString(N_ID_RADIX); + + // parse sparql query + let q_input; + try { + q_input = $$(s_sparql); + } + catch(e) { + return f_okay({ + error: e+'', + }); + } + + // extract graph patterns as a structured graph + let k_patterns = q_input.patterns(); + + // create graphs for this query instance; copy defaults + let h_graphs = {}; + for(let s_key in H_GRAPH_GROUPS) { + h_graphs[s_key] = H_GRAPH_GROUPS[s_key].slice(0); + } + + // set unique graphs for this query + A_UNIQUE_GRAPHS.forEach((s_graph_type) => { + h_graphs[s_graph_type] = ['graph:'+s_graph_type+'_'+s_query_id]; + }); + + // prep transfer hash + let h_transfer = { + $$, + graphs: h_graphs, + patterns: k_patterns, + plugin_router: k_plugin_router, + }; + + // step through the query phases in series + async.series([ + + // relations + (f_next) => { + relations(h_transfer, () => { + local.good('all done with relations'); + f_next(); + }); + }, + ], () => { + + // all done evaluating phases of query + local.good('=== all done evaluating phases of input query ==='); + + // set the default `from` graphs + q_input + // content graphs + .from(h_graphs.content) + // input graph + .from(h_graphs.input); + + // ref query's `order by` conditions + let a_order = q_input.order(); + + // no order clause! + if(!a_order.length) { + // execute input query and return results to callback + return q_input.exec(f_okay); + } + // at least one order clause + else { + + throw 'no order by clause handler yet'; + debugger; + // // examine each order clause + // a_order.forEach((h_order) => { + + // // clause contains function call + // if(h_order.expression && 'functionCall' === h_order.expression.type) { + + // // set resolve to results filter function + // h_transfer.resolve = f_filter_results; + + // // set expression + // h_transfer.expression = h_order.expression; + + // // set extra args for function call + // h_transfer.extra_args = a_extra_args; + + // // evaluate the function call + // solve_query_function_call(h_transfer); + // } + // }); + } + }); + + }, { + + }); +}, { + + /** + * public static: + **/ + + // lazily load compiler + compile(...a_args) { + return require('../compiler/compiler.js').default(...a_args); + }, + +}); + +export default local; diff --git a/lib/volt/evaluator.js b/lib/volt/evaluator.js new file mode 100644 index 0000000..08ac103 --- /dev/null +++ b/lib/volt/evaluator.js @@ -0,0 +1,369 @@ + +import util from 'util'; +import classer from 'classer'; + +const H_PRIMITIVE_DATATYPES_MAP = { + boolean: 'http://www.w3.org/2001/XMLSchema#boolean', + number: 'http://www.w3.org/2001/XMLSchema#decimal', + string: 'http://www.w3.org/2001/XMLSchema#string', +}; + +// +const P_XSD_IRI = 'http://www.w3.org/2001/XMLSchema#'; +const P_XSD_STRING = 'http://www.w3.org/2001/XMLSchema#string'; + +// primitive datatype unboxers +const H_PARSEABLE_DATATYPES = { + 'http://www.w3.org/2001/XMLSchema#boolean': (a) => 'boolean' === typeof a? a: /^t/i.test(a), + 'http://www.w3.org/2001/XMLSchema#string': (a) => a+'', + 'http://www.w3.org/2001/XMLSchema#decimal': (a) => parseFloat(a), + 'http://www.w3.org/2001/XMLSchema#byte': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#unsignedByte': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#short': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#unsignedShort': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#long': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#unsignedLong': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#int': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#unsignedInt': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#integer': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#positiveInteger': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#nonPositiveInteger': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#negativeInteger': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#nonNegativeInteger': (a) => parseInt(a), + 'http://www.w3.org/2001/XMLSchema#float': (a) => parseFloat(a), + 'http://www.w3.org/2001/XMLSchema#double': (a) => parseFloat(a), + 'http://www.w3.org/2001/XMLSchema#dateTime': (a) => new Date(a), +}; + +// +const H_XSD_DATATYPE_PRECEDENCE = { + byte: {up: 'int', level: 5}, + unsignedByte: {up: 'int', level: 5}, + short: {up: 'int', level: 5}, + unsignedShort: {up: 'int', level: 5}, + long: {up: 'integer', level: 4}, + unsignedLong: {up: 'integer', level: 4}, + int: {up: 'integer', level: 4}, + unsignedInt: {up: 'integer', level: 4}, + positiveInteger: {up: 'integer', level: 4}, + nonPositiveInteger: {up: 'integer', level: 4}, + negativeInteger: {up: 'integer', level: 4}, + nonNegativeInteger: {up: 'integer', level: 4}, + integer: {up: 'float', level: 3}, + float: {up: 'double', level: 2}, + double: {up: 'decimal', level: 1}, +}; + +// +const AS_INT = (f_fn) => { + return (...a_args) => { + return { + value: f_fn(...a_args), + type: 'typed-literal', + datatype: 'http://www.w3.org/2001/XMLSchema#integer', + }; + }; +}; + +// +const AS_BOOL = (f_fn) => { + return (...a_args) => { + return { + value: f_fn(...a_args), + type: 'typed-literal', + datatype: 'http://www.w3.org/2001/XMLSchema#boolean', + }; + }; +}; + +const H_NATIVE_OPERATIONS = { + '+': (a, b) => a + b, + '-': (a, b) => a - b, + '*': (a, b) => a * b, + '/': (a, b) => a / b, + '%': (a, b) => a % b, + '&': AS_INT((a, b) => a & b), + '|': AS_INT((a, b) => a | b), + '^': AS_INT((a, b) => a ^ b), + '&&': AS_BOOL((a, b) => a && b), + '||': AS_BOOL((a, b) => a || b), + '>': AS_BOOL((a, b) => a > b), + '<': AS_BOOL((a, b) => a < b), + '>=': AS_BOOL((a, b) => a >= b), + '<=': AS_BOOL((a, b) => a <= b), + '==': AS_BOOL((a, b) => a === b), + '!=': AS_BOOL((a, b) => a !== b), + '===': AS_BOOL((a, b, at, bt) => a === b && at === bt), + '!==': AS_BOOL((a, b, at, bt) => a !== b && at === bt), +}; + +const H_OPERATIONS = { + boolean: H_NATIVE_OPERATIONS, + number: H_NATIVE_OPERATIONS, + string: { + '+': (a, b) => a + b, + }, +}; + +const H_TYPE_COERCION = { + 'boolean/number': H_NATIVE_OPERATIONS, + 'number/string': { + '+': (a, b) => a + b, + '*': (a, b, n, s) => s.repeat(n), + // '/': (a, b, n, s) => s.length + }, +}; + + +const type_value = (z_it) => { + + // ref javascript datatype + let s_type = typeof z_it; + + // primitive + if(H_PRIMITIVE_DATATYPES_MAP[s_type]) { + // convert into ld equivalent + return [H_PRIMITIVE_DATATYPES_MAP[s_type], z_it]; + } + // non-primitive + else { + // sparql entity + if('object' === s_type) { + + // literal + if('typed-literal' === z_it.type) { + // ref datatype iri + let p_datatype = z_it.datatype; + + // lookup if parseable datatype + let f_parser = H_PARSEABLE_DATATYPES[p_datatype]; + + // datatype is parseable + if(f_parser) { + // return with parsed value + return [p_datatype, f_parser(z_it.value)]; + } + // datatype not parseable + else { + // return with value as string + return [p_datatype, z_it.value]; + } + } + else { + local.fail(`cannot handle ${z_it.type} types of entities, only typed-literals`); + } + } + // other + else { + local.fail(`should not be encountering ${s_type} value in expression evaluation: ${z_it}`); + } + } +}; + +// +const as_literal = (w_value, p_type_a, p_type_b) => { + // type has already been decided + if('object' === typeof w_value && w_value.datatype) { + return w_value; + } + + // start with a's type + let p_negotiated_datatype = p_type_a; + + // b has different type + if(p_negotiated_datatype !== p_type_b) { + // both are xsd + if(p_type_a.startsWith(P_XSD_IRI) && p_type_b.startsWith(P_XSD_IRI)) { + // extract type from suffix of datatype iri + let s_type_a = p_type_a.substr(P_XSD_IRI.length); + let s_type_b = p_type_b.substr(P_XSD_IRI.length); + + // lookup their precedence level + let n_level_a = H_XSD_DATATYPE_PRECEDENCE.hasOwnProperty(s_type_a)? H_XSD_DATATYPE_PRECEDENCE[s_type_a].level: 0; + let n_level_b = H_XSD_DATATYPE_PRECEDENCE.hasOwnProperty(s_type_b)? H_XSD_DATATYPE_PRECEDENCE[s_type_b].level: 0; + + // negotiate highest fair level + let n_target_level = Math.min(n_level_a, n_level_b); + + // iterate up until they are at the same level + while(n_level_a > n_target_level) { + s_type_a = H_XSD_DATATYPE_PRECEDENCE[s_type_a].up; + n_level_a = H_XSD_DATATYPE_PRECEDENCE.hasOwnProperty(s_type_a)? H_XSD_DATATYPE_PRECEDENCE[s_type_a].level: 0; + } + while(n_level_b > n_target_level) { + s_type_b = H_XSD_DATATYPE_PRECEDENCE[s_type_b].up; + n_level_b = H_XSD_DATATYPE_PRECEDENCE.hasOwnProperty(s_type_b)? H_XSD_DATATYPE_PRECEDENCE[s_type_b].level: 0; + } + + // iterate up until they are the same type + while(s_type_a !== s_type_b && n_target_level > 0) { + let h_link_a = H_XSD_DATATYPE_PRECEDENCE[s_type_a]; + n_target_level = h_link_a.level; + s_type_a = h_link_a.up; + s_type_b = H_XSD_DATATYPE_PRECEDENCE[s_type_b].up; + } + + // datatypes still not the same + if(s_type_a !== s_type_b) { + local.fail(`failed to negotiate types`); + } + + // render this as negotiated datatype + p_negotiated_datatype = P_XSD_IRI+s_type_a; + } + else { + local.fail(`failed to negotiate non-xsd datatypes <${p_type_a}> and <${p_type_b}>`); + } + } + + // + return { + value: w_value, + type: 'typed-literal', + datatype: p_negotiated_datatype, + }; +}; + + +/** +* class: +**/ +const local = classer('evaluator', (h_config, f_okay) => { + + // + let { + $$, + plugin_router: k_plugin_router, + } = h_config; + + // + let n_expected_yields = 1; + let a_yields = []; + + // + const h_helpers = { + + // evaluate an expression using an operator symbol + x(z_lhs, s_operator, z_rhs) { + + // get type and value of each term + let [p_lhs_type, z_lhs_value] = type_value(z_lhs); + let [p_rhs_type, z_rhs_value] = type_value(z_rhs); + + // ref lhs javascript type + let s_lhs_js_type = typeof z_lhs_value; + let s_rhs_js_type = typeof z_rhs_value; + + // values are compatible for the same operation + if(s_lhs_js_type === s_rhs_js_type) { + // ensure they are not strings while datatypes are not xsd:string + if('string' !== s_lhs_js_type || P_XSD_STRING === p_lhs_type) { + // ref operation group + let h_operation_group = H_OPERATIONS[s_lhs_js_type]; + + // no such group?! + if(!h_operation_group) { + local.fail(`no operations can be performed on ${s_lhs_js_type} types`); + } + + // lookup operation based on type + let f_operation = h_operation_group[s_operator]; + + // no such operation + if(!f_operation) { + local.fail(`no such operation '${s_operator}' exists between values of types ${s_lhs_js_type}`); + } + + // execute operation + let w_value = f_operation(z_lhs_value, z_rhs_value, p_lhs_type, p_rhs_type); + + // create literal + return as_literal(w_value, p_lhs_type, p_rhs_type); + } + } + + local.fail('mixed type operations not yet supported'); + }, + + // evaluate a function call asynchronously + call(p_function, a_args, f_okay_call) { + + // let plugin router handle it + k_plugin_router(p_function, a_args, (w_result) => { + + // pass result to callback + f_okay_call(w_result); + }); + }, + + // evaluate truthiness of value as boolean + as: { + boolean(z_value) { + // already a primitive + if('boolean' === typeof z_value) { + return z_value; + } + // sparql literal + else if('object' === typeof z_value) { + // parse the value to javascript equivalent + let [, z_parsed_value] = type_value(z_value); + + // truthiness + return !!z_parsed_value; + } + // other + else { + local.fail(`failed to cast ${typeof z_value} value to boolean: ${z_value}`); + } + }, + }, + + // + sparql: $$, + + // + yield_(b_positive, h_triple) { + + // save yield result to list + a_yields.push({ + positive: b_positive, + triple: h_triple && [h_triple.subject, h_triple.predicate, h_triple.object], + }); + + // this is the last yield + if(n_expected_yields === a_yields.length) { + + // return results to callback + f_okay(a_yields); + } + }, + + // traverse each item in list and account for increased number of yields + each(a_list, f_each) { + + // the number of expected yields multiplies with each new for-each loop + n_expected_yields *= a_list.length; + + // apply iteration + a_list.forEach(f_each); + }, + }; + + // + return h_helpers; +}, { + + /** + * public static: + **/ + + destruct(s_arg_name) { + // create dummy object + let k_dummy = local(); + + // use hash to create destruct string + return `let {${Object.keys(k_dummy).join(', ')}} = ${s_arg_name};`; + }, +}); + +export default local; diff --git a/lib/volt/extend-safely.js b/lib/volt/extend-safely.js new file mode 100644 index 0000000..d2ae877 --- /dev/null +++ b/lib/volt/extend-safely.js @@ -0,0 +1,106 @@ +/** +* import: +**/ + +// native imports + +// libraries +import arginfo from 'arginfo'; + +// local classes + +// meta class setup +const __class_name = 'ExtendSafely'; + +// +const is_extendable = function(z_value, b_no_functions=false) { + + // ref value type + let s_type = typeof z_value; + + // object + if('object' === s_type) { + + // don't try to extend non-simple objects + if(null === z_value + || Object.prototype !== Object.getPrototypeOf(z_value) + ) return false; + + // value is extendable + return true; + } + // function + else if('function' === s_type && !b_no_functions) { + + // value is extendable + return true; + } + + // value is not extendable + return false; +}; + +// +const extend = function(h_target, h_object) { + + // each key in object being used to extend target + for(let s_key in h_object) { + + // key exists + if(h_target.hasOwnProperty(s_key)) { + + // ref existing value + let z_value = h_target[s_key]; + + // ref object's value + let z_object_value = h_object[s_key]; + + // existing value is extendable & object's value wants to extend + if(is_extendable(z_value) && is_extendable(z_object_value, true)) { + + // extend value + extend(z_value, h_object[s_key]); + + // do not overwrite it + continue; + } + } + + // put / overwrite value @key + h_target[s_key] = h_object[s_key]; + } +}; + + +/** +* public static operator() (): +**/ +const local = function(z_target, ...a_objects) { + + // each object that will be used to extend target + a_objects.forEach((h_object) => { + + // extend target using object + extend(z_target, h_object); + }); + + // return mutated target + return z_target; +}; + + +/** +* public static: +**/ +{ + + // + local.toString = function() { + return __class_name+'()'; + }; + + // prefix output messages to console with class's tag + require(__dirname+'/../console/log-tag.js').extend(local, __class_name); +} + +export default local; diff --git a/lib/volt/plugins.js b/lib/volt/plugins.js new file mode 100644 index 0000000..c1781b4 --- /dev/null +++ b/lib/volt/plugins.js @@ -0,0 +1,118 @@ + +import classer from 'classer'; +import qs from 'qs'; + +// +const R_FUNCTION_URI = /^([^]+?)(?:\?(.+)&function=(\w+))?$/; + + + +// +const local = classer('plugins', (h_plugin_modules, h_util) => { + + // + let h_router = {}; + + // add all plugins from hash + for(let p_namespace in h_plugin_modules) { + h_router[p_namespace] = {}; + } + + // + return function(p_function, a_args, f_okay) { + + // prep namespace uri + let p_namespace; + + // prep user config hash + let h_user_config = {}; + + // parse function uri + let [, p_prefix, s_querystring, s_function_name] = R_FUNCTION_URI.exec(p_function); + + // querystring-style uri + if(s_function_name) { + // no plugin exists in namespace + if(!h_router.hasOwnProperty(p_prefix)) { + debugger; + throw 'no plugin is registered that matches namespace'; + } + + // set namespace + p_namespace = p_prefix; + } + // no querystring, search for namespace + else { + // set default querystring + s_querystring = ''; + + // find which namespaces match + let a_namespace_matches = Object.keys(h_plugin_modules).filter(p_ns => p_prefix.startsWith(p_ns)); + + // no matching namespace + if(!a_namespace_matches.length) { + debugger; + throw 'no plugin is registered that matches namespace'; + } + // multiple namespaces + else if(a_namespace_matches.length > 1) { + debugger; + throw 'multiple namespaces occupy same prefix'; + } + // single namespace + else { + // set namespace + p_namespace = a_namespace_matches[0]; + + // extract function name + s_function_name = p_function.substr(p_namespace.length); + } + } + + // prep plugin instance + let k_plugin_instance; + + // ref plugin + let h_plugin = h_router[p_namespace]; + + // plugin not yet reserved + if(!h_plugin) { + // claim the namespace + h_plugin = h_router[p_namespace] = {}; + } + + // user config instance does not yet exist + if(!h_plugin.hasOwnProperty(s_querystring)) { + // parse querystring using default options from qs module + h_user_config =s_querystring.length? qs.parse(s_querystring): {}; + + // lazily create instance with user config + k_plugin_instance = h_plugin[s_querystring] = h_plugin_modules[p_namespace](h_user_config); + + // plugin did not return anything useful + let s_instance_type = typeof k_plugin_instance; + if('object' !== s_instance_type && 'function' !== s_instance_type) { + debugger; + throw 'plugin did not produce a routable datatype'; + } + } + // instance exists + else { + // ref instance + k_plugin_instance = h_plugin[s_querystring]; + } + + // function does not exist + if(!k_plugin_instance.hasOwnProperty(s_function_name)) { + debugger; + throw 'function not exists in plugin'; + } + // function exists + else { + // apply function to input args + k_plugin_instance[s_function_name](f_okay, ...a_args); + } + }; +}); + +export default local; diff --git a/lib/volt/relations.js b/lib/volt/relations.js new file mode 100644 index 0000000..b949c56 --- /dev/null +++ b/lib/volt/relations.js @@ -0,0 +1,282 @@ +// native imports +import util from 'util'; + +// third-party libraries +import async from 'async'; +import classer from 'classer'; +import graphy from 'graphy'; + +// local classes +import builder from './builder'; +import evaluator from './evaluator'; + + + +/** +* private static: +**/ + + +/** +* globals: +**/ + +const h_catalog = {}; + +const local = classer('relations', (...a_args) => { + return new relations_inspector(...a_args); +}); + +/** +* class: +**/ + +class relations_inspector { + + constructor(h_transfer, f_okay_all) { + + // destruct config + let { + $$, + graphs: h_graphs, + patterns: k_patterns, + plugin_router: k_plugin_router, + } = h_transfer; + + // prep async task queue + let a_tasks = []; + + // + Object.assign(this, { + $$, + graphs: h_graphs, + tasks: a_tasks, + plugin_router: k_plugin_router, + }); + + /** + * main: + **/ + + // each top-level pattern in graph + for(let [h_triple, s_subject, s_predicate, s_object] of k_patterns.top_level()) { + + // subject is a blanknode; skip this triple + if(s_subject.startsWith('_:')) continue; + + // object is a variable or an iri + if('?' === s_object[0] || $$.isIri(s_object)) { + + // predicate is a variable + if('?' === s_predicate[0]) { + + // // inspect closure of predicate variable + // inspect_variable_predicate(h_transfer); + } + // predicate is a named thing + else if($$.isNamedPredicate(s_predicate)) { + + // + let h_trigger = { + subject: s_subject, + predicate: s_predicate, + object: s_object, + }; + + // subject and object are uris (ie, they are known) + if($$.isIri(s_subject) && $$.isIri(s_object)) { + // go async + a_tasks.push((f_okay_relation) => { + + // so then is this solution already cached? + $$.ask([s_subject, s_predicate, s_object].map($$.iri)) + .from(h_graphs.content) + .answer((b_solution_exists) => { + + // solution is not cached; inspect relation + if(!b_solution_exists) { + this.inspect_named_predicate(h_trigger); + } + + // task complete + return f_okay_relation(); + }); + }); + } + // subject, object or both are variables; must inspect relation + else { + this.inspect_named_predicate(h_trigger); + } + } + } + } + + // no tasks were added; return immediately + if(!a_tasks.length) return f_okay_all(); + + // otherwise, there are tasks to process + async.parallel(a_tasks, () => { + local.good('==== all tasks completed ===='); + + // all done + f_okay_all(); + }); + } + + // + inspect_named_predicate(h_trigger) { + + // destruct members + let { + $$, + graphs: h_graphs, + tasks: a_tasks, + } = this; + + // destruct input + let { + subject: s_subject, + predicate: p_predicate, // relation is known to be absolute uri + object: s_object, + } = h_trigger; + + // lookup relation cache already loaded in memory + let f_relation = h_catalog[p_predicate]; + + // relation function needs to be loaded from graph + if(!f_relation) { + // go async + a_tasks.push((f_okay_task) => { + + // discover volt relation by name + $$.describe('?relation') + .from(h_graphs.model) + .where( + ['?relation', { + a: 'volt:Relation', + 'volt:name': `<${p_predicate}>`, + }] + ) + .dump() + .browse((h_jsonld) => { + + // create graphy instance; then create graphy network in volt: namespace + graphy(h_jsonld, (k_network) => { + + // ref list of entities at top of graph + let a_entities = k_network.top(w => w.$('volt:')); + + // no relations were found by this name + if(!a_entities.length) { + // complete; short-circuit out of function + return f_okay_task(); + } + // more than one relation share same name + else if(a_entities.length > 1) { + // cannot resolve uniquely + return local.fail(`multiple volt relations share the name uri: <${p_predicate}>`); + } + + // ref relation's graphy node + let k_relation = a_entities[0]; + + // compile into javascript function + f_relation = builder({ + node: k_relation, + }); + + // save to cache + h_catalog[p_predicate] = f_relation; + + // excute function + this.execute(f_relation, h_trigger, f_okay_task); + }); + }); + }); + } + // relation cache is available in memory + else { + // execute immediately + a_tasks.push((f_okay_task) => { + this.execute(f_relation, h_trigger, f_okay_task); + }); + } + } + + // + execute(f_relation, h_trigger, f_okay_relation) { + + // destruct members + let { + $$, + graphs: h_graphs, + plugin_router: k_plugin_router, + } = this; + + // args to inject into relation functions at runtime + let a_injection_args = [ + // util: support functions + evaluator({ + $$: { + // proxy spaz select call by choosing graphs too + select(...a_args) { + return $$.select(...a_args).from(h_graphs.content); + }, + }, + plugin_router: k_plugin_router, + }, (a_yields) => { + + // prep hash to produce unique array of triples + let h_truths = {}; + + // materialize positive results + a_yields.forEach((h_yield) => { + if(h_yield.positive) { + let s_triple = h_yield.triple.map($$.turtle).join(' '); + h_truths[s_triple] = 1; + } + }); + + // unique array of truth triples + let a_truths = Object.keys(h_truths); + + // no positive results + if(!a_truths.length) { + // done + f_okay_relation(); + } + // yes positive results + else { + // insert them into output graph + $$.insert + .into(h_graphs.output[0]) + .data(...a_truths) + .then(() => { + f_okay_relation(); + }); + } + }), + ]; + + // + let s_query_supplement = ''; + + if($$.isIri(h_trigger.subject)) { + s_query_supplement += `values ?_subject { ${$$.iri(h_trigger.subject)} }`; + } + + if($$.isIri(h_trigger.object)) { + s_query_supplement += `values ?_object { ${$$.iri(h_trigger.subject)} }`; + } + + // + f_relation.apply({ + query_supplement: s_query_supplement, + subject: {type: 'uri', value: h_trigger.subject}, + predicate: {type: 'uri', value: h_trigger.predicate}, + object: {type: 'uri', value: h_trigger.object}, + }, a_injection_args); + } +} + +export default local; diff --git a/lib/volt/volt.js b/lib/volt/volt.js new file mode 100644 index 0000000..3d39acc --- /dev/null +++ b/lib/volt/volt.js @@ -0,0 +1,527 @@ +/** +* import: +**/ + +// libraries +import async from 'async'; +import arginfo from 'arginfo'; +import classer from 'classer'; +import clone from 'clone'; +import clc from 'cli-color'; +import graphy from 'graphy'; +import merge from 'merge'; +import spaz from 'spaz'; +// import emoji from 'node-emoji'; + +// local classes +// import volt_values from './volt-values.js'; +// import volt_subqueries from './volt-subqueries.js'; +// import volt_properties from './volt-properties.js'; +// import volt_methods from './volt-methods.js'; + +import properties from './properties'; +import plugins from './plugins'; + + +/** +* private static: +**/ + +// creates a colorized terminal string of the given spaz query builder; for debugging purposes +const sparql_str = (q_query) => { + let s_sparql = q_query.toSparql({meaty: true, indent: ' '}); + s_sparql = s_sparql.replace(/^|\n/g, '\n '); + return clc.cyan(s_sparql); +}; + +// +const A_UNIQUE_GRAPHS = [ + 'input', + 'cache', +]; + +// +const H_GRAPHS = { + content: ['graph:source', 'graph:reference', 'graph:output'], + model: ['graph:model', 'graph:reference'], + output: ['graph:output'], + fallacy: ['graph:fallacy'], + solution: ['graph:output', 'graph:fallacy'], +}; + +// +let i_query_uuid = 0; +const next_query_uuid = () => { + return i_query_uuid++; +}; + +// +let h_issues = {}; +const issue = (s_err) => { + if(!h_issues[s_err]) { + h_issues[s_err] = 1; + } + else { + h_issues[s_err] += 1; + } +}; + + +// prefixes to load into spaz by default +const H_DEFAULT_PREFIXES = { + '': 'vocab://local/', + + unit: 'http://qudt.org/vocab/unit#', + qudt: 'http://qudt.org/schema/qudt#', +}; + +// prefixes reserved and used by volt, mandatory for n3 queries +const H_REQUIRED_PREFIXES = { + graph: 'graph://', + volt: 'http://volt-name.space/ontology/', + input: 'http://volt-name.space/vocab/input#', + output: 'http://volt-name.space/vocab/output#', + stko: 'http://stko.geog.ucsb.edu/vocab/', +}; + + +/** +* class +**/ +const local = classer('Volt', function(h_config) { + + /** + * private: + **/ + + let k_plugin_router = {}; + + // argument type-checking + (() => { + if('object' !== typeof h_config) { + local.fail('constructor\'s config argument must be [object]. instead got: '+arginfo(h_config)); + } + if('string' !== typeof h_config.endpoint) { + local.fail('constructor\'s config.endpoint argument must be [string]. instead got: '+arginfo(h_config)); + } + })(); + + // open abstract connection to SPARQL endpoint + let $$ = spaz({ + engine: { + endpoint: h_config.endpoint, + http_methods: 'post', + }, + prefixes: merge( + H_DEFAULT_PREFIXES, + h_config.prefixes || {}, + H_REQUIRED_PREFIXES + ), + }); + + + // plugins are present + if(h_config.plugins) { + + // load plugins (create plugin router) + k_plugin_router = plugins(h_config.plugins, { + $$, // pass spaz instance + }); + } + + + // + const execute_function_call = (s_function, a_args, h_row, f_okay_call) => { + + // + let a_input_args = a_args.map($$.rabbit).map((h_arg) => { + if('variable' === h_arg.type) { + + // ref var name + let s_var = h_arg.name; + + if(h_row.hasOwnProperty(s_var)) { + return h_row[s_var]; + } + else { + local.fail('variable not defined: '+s_var); + } + } + }); + + // + k_plugin_router(s_function, a_input_args, (s_err, h_result) => { + local.warn('function call returned'); + if(s_err) { + local.fail(s_err); + } + else { + f_okay_call(h_result, a_input_args); + } + }); + }; + + + // + const solve_query_function_call = (h_opt) => { + + // destruct options arg hash + let { + expression: h_expr, + graphs: w_graphs, + input_query: q_input, + resolve: f_results, + extra_args: a_extra_args, + } = h_opt; + + // extract limit / offset + let n_limit = q_input.limit(); + let n_offset = q_input.offset(); + + // extract select variables + let w_select = q_input.select(true); + + // reset affected clauses + q_input.order.clear(); + + // set limit / offset + q_input.limit(0); + q_input.offset(0); + + // execute input query; collect all results + q_input.select('*').rows(function(a_results) { + + // + local.info('input query: '+sparql_str(this)+' => '+arginfo(a_results)); + + // no results + if(!a_results.length) { + + // callback immediately + f_results(a_results); + + // don't bother ordering empty resutls + return; + } + + // prepare list of tasks + let a_tasks = []; + + // each result + a_results.forEach((h_row) => { + + // create new task + a_tasks.push((f_okay_task) => { + + // evaluate function call on these args + execute_function_call(h_expr.function, h_expr.args, h_row, (z_return_value, a_input_args) => { + + // insert results + let q_function_eval = $$.insert + .into(w_graphs.cache[0]) + .data({ + a: 'volt:FunctionCall', + 'volt:function': '<'+h_expr.function+'>', + 'volt:arguments': $$.list(a_input_args), + 'volt:returned': $$.val(z_return_value), + }); + + local.info('function eval "'+h_expr.function+'"\n'+sparql_str(q_function_eval)); + + q_function_eval.then(() => { + f_okay_task(null); + }); + }); + }); + + local.good(a_tasks.length+' function call tasks'); + }); + + // execute tasks in parallel + async.parallel(a_tasks, () => { + + local.good('sample result value: '+arginfo(a_results[0])); + + // once all tasks complete, do query to order by return value + let q_order = $$.select(w_select) + .from(w_graphs.cache[0]) + .where({ + a: 'volt:FunctionCall', + 'volt:function': '<'+h_expr.function+'>', + 'volt:arguments': $$.list(h_expr.args), + 'volt:returned': '?returnValue', + }) + .values(a_results) + .order('?returnValue') + .limit(n_limit) + .offset(n_offset); + + // output query before execution + local.info('order query: '+sparql_str(q_order)); + + // execute + q_order + .rows((h_result) => { + + // clear cache graph + $$.silently.drop.graph(w_graphs.cache[0]); + + // send back results + f_results(h_result); + }); + }); + }); + }; + + // + const execute_methods = (q_input, k_patterns, f_okay_methods) => { + f_okay_methods(); + }; + + // + const execute_input_query = (h_transfer, f_results, a_extra_args) => { + + // destruct transfer object + let { + graphs: w_graphs, + input_query: q_input, + query_uuid: s_query_uuid, + input_mapping: h_input_mapping, + } = h_transfer; + + // + local.info('fixing input mappings on input query: '+sparql_str(q_input)); + + // each input iri in input mapping + for(let p_input_iri in h_input_mapping) { + let h_input = h_input_mapping[p_input_iri]; + + // recreate original variable + let s_variable = '?'+h_input.field; + + // replace substitute iri with variable + q_input.where.replace(p_input_iri, h_input.n3); + + // // plugin a values block for this mapping + // q_input.values(s_variable, [$$.iri(p_input_iri)]); + } + + // + local.warn('executing input query: '+sparql_str(q_input)); + + // execute no matter the type; forward response to waiter + q_input.exec(f_results); + + + // // input query type: ASK + // if('ask' === q_input.query_type) { + + // // + // q_input.answer(f_results, ...a_extra_args); + // } + // // input query type: SELECT + // if('select' === q_input.query_type) { + + // // + // q_input.rows(f_results, ...a_extra_args); + // } + // // input query type: DESCRIBE + // if('describe' === q_input.query_type) { + + // // + // q_input.browse(f_results, ...a_extra_args); + // } + }; + + + // + const query_from_sparql = (s_query_uuid, s_input_query, f_results, ...a_extra_args) => { + + // + let q_input; + + // instantiate query builder on input in order to extract query patterns + try { + q_input = $$(s_input_query); + } + catch(e) { + f_results({ + error: e+'', + }); + return; + } + + // + query_from_spaz_node(s_query_uuid, q_input, f_results, ...a_extra_args); + }; + + + // + const query_from_spaz_node = (s_query_uuid, q_input, f_results, ...a_extra_args) => { + + // adds additional info to certain result types + let f_filter_results = (h_results) => { + + // select query + if(h_results.results) { + + // inspect bindings + let a_bindings = h_results.results.bindings; + if(Array.isArray(a_bindings)) { + + // each row + a_bindings.forEach((h_row) => { + + // each field in row + for(let s_var in h_row) { + let h_field = h_row[s_var]; + + // depending on n3 type + switch(h_field.type) { + + // iri + case 'uri': + // include `.terse` property + h_field.terse = q_input.shorten(h_field.value); + break; + } + } + }); + } + } + + // return results + f_results(h_results); + }; + + // get a proxy to the graph patterns as a structured graph + let k_patterns = q_input.patterns(); + + // create graphs for this query instance; use clone of defaults + let w_graphs = clone(H_GRAPHS, false); + + // set unique graphs for this query + A_UNIQUE_GRAPHS.forEach((s_graph_type) => { + w_graphs[s_graph_type] = ['graph:'+s_graph_type+'_'+s_query_uuid]; + }); + + // prepare boiler-plate transfer object + let h_transfer = { + $$, + graphs: w_graphs, + plugin_router: k_plugin_router, + input_query: q_input, + patterns: k_patterns, + query_uuid: s_query_uuid, + input_mapping: {}, + }; + + // execute order of operations on input patterns + async.series([ + + // // values interceptor + // (f_okay_task) => { + + // // set resolve callback + // h_transfer.resolve = f_okay_task; + + // // execute interceptor + // volt_values(h_transfer); + // }, + + // // subquery interceptor + // (f_okay_task) => { + + // // set resolve callback + // h_transfer.resolve = f_okay_task; + + // // execute interceptor + // volt_subqueries(h_transfer); + // }, + + // properties + (f_okay_task) => { + + // set resolve callback + h_transfer.resolve = f_okay_task; + + // execute properties + new properties(h_transfer); + }, + + // // methods + // (f_okay_task) => { + + // // set resolve callback + // h_transfer.resolve = f_okay_task; + + // // execute methods + // volt_methods(h_transfer); + // }, + ], (h_err) => { + + local.log('=== all series tasks completed ==='); + + // set the default from graphs + q_input + // content graphs + .from(w_graphs.content) + // input graph + .from(w_graphs.input); + + // ref order conditions + let a_order = q_input.order(); + + // no order clause! + if(!a_order.length) { + execute_input_query(h_transfer, f_filter_results, a_extra_args); + } + // at least one order clause + else { + + // examine each order clause + a_order.forEach((h_order) => { + + // clause contains function call + if(h_order.expression && 'functionCall' === h_order.expression.type) { + + // set resolve to results filter function + h_transfer.resolve = f_filter_results; + + // set expression + h_transfer.expression = h_order.expression; + + // set extra args for function call + h_transfer.extra_args = a_extra_args; + + // evaluate the function call + solve_query_function_call(h_transfer); + } + }); + } + }); + }; + + + /** + * public operator(): + **/ + + // parse a SPARQL string and return a query-builder + return classer.operator(function(...a_args) { + return query_from_sparql(next_query_uuid(), ...a_args); + }, { + // clear input graph + reset() { + $$.silently.drop.graph('graph:input_0'); + }, + }); +}, { + /** + * public static: + **/ + +}); + + +export default local; diff --git a/package.json b/package.json new file mode 100644 index 0000000..0a5d653 --- /dev/null +++ b/package.json @@ -0,0 +1,84 @@ +{ + "name": "volt", + "version": "0.0.3", + "description": "Volt Ontology and Linked-data Technology", + "author": "Blake Regalia (http://blake-regalia.com)", + "license": "ISC", + "repository": "blake-regalia/volt", + "files": [ + "dist" + ], + "main": "dist/volt/engine.js", + "keywords": [ + "volt", + "ontology", + "linked-data", + "semantic-web" + ], + "scripts": { + "test": "gulp" + }, + "install": "cd ./node_modules/ace/tool && npm install", + "engines": { + "node": ">=1.0.0", + "npm": ">=1.1.65" + }, + "dependencies": { + "body-parser": "^1.14.2", + "classer": "^0.3.0", + "cli-color": "^1.1.0", + "clone": "^1.0.2", + "extend": "^3.0.0", + "graphy": "^1.3.1", + "merge": "^1.2.0", + "n3": "^0.4.5", + "node-uuid": "^1.4.7", + "qs": "^6.2.0", + "rapunzel": "^0.1.10", + "spaz": "^0.1.32", + "turtle-validator": "^1.0.1" + }, + "devDependencies": { + "amd-loader": "0.0.5", + "babel-cli": "^6.4.5", + "babel-core": "^6.8.0", + "babel-eslint": "^4.1.8", + "babel-loader": "^6.2.1", + "babel-plugin-transform-runtime": "^6.4.3", + "babel-preset-es2015": "^6.3.13", + "del": "^2.2.0", + "ebnf-parser": "^0.1.10", + "extend": "^3.0.0", + "glob": "^7.0.3", + "gulp": "^3.9.0", + "gulp-babel": "^6.1.2", + "gulp-cached": "^1.1.0", + "gulp-concat": "^2.6.0", + "gulp-coveralls": "^0.1.4", + "gulp-debug": "^2.1.2", + "gulp-eslint": "^1.1.1", + "gulp-exclude-gitignore": "^1.0.0", + "gulp-istanbul": "^0.10.3", + "gulp-jison": "^1.2.0", + "gulp-load-plugins": "^1.2.0", + "gulp-mocha": "^2.2.0", + "gulp-node-inspector": "^0.1.0", + "gulp-nsp": "^2.3.0", + "gulp-plumber": "^1.0.1", + "gulp-rename": "^1.2.2", + "gulp-replace": "^0.5.4", + "gulp-sourcemaps": "^1.6.0", + "gulp-util": "^3.0.7", + "gulp-yaml": "^1.0.1", + "isparta": "^4.0.0", + "jison": "^0.4.17", + "js-yaml": "^3.5.2", + "lex-parser": "^0.1.4", + "mkdirp": "^0.5.1", + "plist": "^1.2.0", + "progress": "^1.1.8", + "requestretry": "^1.6.0", + "should": "^8.2.1", + "through2": "^2.0.1" + } +}