From bc1285bc374ba293138500359a65a33bdc919eaf Mon Sep 17 00:00:00 2001 From: Blake Regalia Date: Thu, 2 Jun 2016 15:03:07 +0300 Subject: [PATCH] restarted repo with clean code commit --- .babelrc | 3 + .eslintrc | 147 +++++ .gitignore | 67 +++ LICENSE | 14 + README.md | 45 ++ config.module.js | 11 + debug | 6 + debug-language | 7 + gulp/ace-mode.js | 50 ++ gulp/clean.js | 10 + gulp/develop.js | 16 + gulp/jison.js | 89 +++ gulp/load-tasks.js | 145 +++++ gulp/sublime.js | 27 + gulp/test.js | 33 ++ gulp/tm-language.js | 60 ++ gulp/tm-preferences.js | 16 + gulp/transpile.js | 35 ++ gulpfile.babel.js | 39 ++ lib/compiler/assembler.js | 215 ++++++++ lib/compiler/ast.js | 364 +++++++++++++ lib/compiler/compiler.js | 449 +++++++++++++++ lib/compiler/optimizer.js | 69 +++ lib/compiler/rapunzel.js | 122 +++++ lib/compiler/router.js | 104 ++++ lib/compiler/serializer.js | 50 ++ lib/compiler/volt.jison | 364 +++++++++++++ lib/compiler/volt.jisonlex | 227 ++++++++ lib/syntax-lex/lex.YAML-tmLanguage | 207 +++++++ lib/syntax-lex/lex.tmPreferences | 34 ++ lib/syntax-volt/volt.YAML-tmLanguage | 691 +++++++++++++++++++++++ lib/syntax-volt/volt.tmPreferences | 46 ++ lib/volt/builder.js | 784 +++++++++++++++++++++++++++ lib/volt/engine.js | 208 +++++++ lib/volt/evaluator.js | 369 +++++++++++++ lib/volt/extend-safely.js | 106 ++++ lib/volt/plugins.js | 118 ++++ lib/volt/relations.js | 282 ++++++++++ lib/volt/volt.js | 527 ++++++++++++++++++ package.json | 84 +++ 40 files changed, 6240 insertions(+) create mode 100644 .babelrc create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config.module.js create mode 100755 debug create mode 100755 debug-language create mode 100644 gulp/ace-mode.js create mode 100644 gulp/clean.js create mode 100644 gulp/develop.js create mode 100644 gulp/jison.js create mode 100644 gulp/load-tasks.js create mode 100644 gulp/sublime.js create mode 100644 gulp/test.js create mode 100644 gulp/tm-language.js create mode 100644 gulp/tm-preferences.js create mode 100644 gulp/transpile.js create mode 100644 gulpfile.babel.js create mode 100644 lib/compiler/assembler.js create mode 100644 lib/compiler/ast.js create mode 100644 lib/compiler/compiler.js create mode 100644 lib/compiler/optimizer.js create mode 100644 lib/compiler/rapunzel.js create mode 100644 lib/compiler/router.js create mode 100644 lib/compiler/serializer.js create mode 100644 lib/compiler/volt.jison create mode 100644 lib/compiler/volt.jisonlex create mode 100644 lib/syntax-lex/lex.YAML-tmLanguage create mode 100644 lib/syntax-lex/lex.tmPreferences create mode 100644 lib/syntax-volt/volt.YAML-tmLanguage create mode 100644 lib/syntax-volt/volt.tmPreferences create mode 100644 lib/volt/builder.js create mode 100644 lib/volt/engine.js create mode 100644 lib/volt/evaluator.js create mode 100644 lib/volt/extend-safely.js create mode 100644 lib/volt/plugins.js create mode 100644 lib/volt/relations.js create mode 100644 lib/volt/volt.js create mode 100644 package.json 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" + } +}