├── src ├── daisneon │ ├── bridge.cljs │ ├── example.cljs │ └── dais.cljs ├── dais.js ├── index.js └── lib.cpp ├── native ├── build.rs ├── Cargo.toml ├── src │ └── lib.rs └── Cargo.lock ├── binding.gyp ├── .gitignore ├── package.json ├── LICENSE ├── Makefile └── README.md /src/daisneon/bridge.cljs: -------------------------------------------------------------------------------- 1 | (ns daisneon.bridge) 2 | 3 | (def rust (js/require "./native/index.node")) 4 | (def cpp (js/require "./build/Release/cppaddon.node")) 5 | 6 | -------------------------------------------------------------------------------- /native/build.rs: -------------------------------------------------------------------------------- 1 | extern crate neon_build; 2 | 3 | fn main() { 4 | neon_build::setup(); // must be called in build.rs 5 | 6 | // add project-specific build logic here... 7 | } 8 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "include_dirs": [ 5 | ""] 5 | license = "MIT" 6 | build = "build.rs" 7 | 8 | [lib] 9 | name = "daisneon" 10 | crate-type = ["dylib"] 11 | 12 | [build-dependencies] 13 | neon-build = "0.1.20" 14 | 15 | [dependencies] 16 | neon = "0.1.20" 17 | itertools = "0.6.5" 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project related 2 | 3 | # Java related 4 | pom.xml 5 | pom.xml.asc 6 | *jar 7 | *.class 8 | 9 | # Leiningen 10 | classes/ 11 | lib/ 12 | checkouts/ 13 | target/ 14 | .lein-* 15 | repl-port 16 | .nrepl-port 17 | .repl 18 | 19 | # Node / Neon 20 | native/target 21 | native/index.node 22 | native/artifacts.json 23 | node_modules/ 24 | 25 | # Temp Files 26 | *.orig 27 | *~ 28 | .*.swp 29 | .*.swo 30 | *.tmp 31 | *.bak 32 | 33 | # OS X 34 | .DS_Store 35 | 36 | # Logging 37 | *.log 38 | /logs/ 39 | 40 | # Builds 41 | out/ 42 | build/ 43 | 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daisneon", 3 | "version": "0.1.0", 4 | "description": "Dais interceptor chain in Rust+Node.js", 5 | "main": "src/index.js", 6 | "author": "Paul deGrandis ", 7 | "license": "MIT", 8 | "repository": "https://github.com/ohpauleez/daisneon", 9 | "dependencies": { 10 | "neon-cli": "^0.1.20", 11 | "lumo-cljs": "1.8.0-beta", 12 | "source-map-support": "0.4.18", 13 | "nan": "^2.7.0", 14 | "node-gyp": "^3.6.2" 15 | }, 16 | "scripts": { 17 | "cljs-repl": "lumo -c src:native:build:target:. --repl", 18 | "cljs-socketrepl": "lumo -c src:native:build:target:. --socket-repl 5555", 19 | "compile-cljs": "lumo -c src build.cljs dev", 20 | "compile-rust": "neon build", 21 | "compile-cpp": "node-gyp rebuild", 22 | "install": "node-gyp rebuild && neon build && lumo -c src build.cljs" 23 | }, 24 | "gypfile": true 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2017, Paul deGrandis All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | SHELL := /usr/bin/env bash 3 | 4 | # This path is only accurate when calling with `make -f` directly 5 | # In a shell-wrap situation it resolves to the temp file descriptor 6 | MAKEFILE_PATH := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 7 | 8 | RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) 9 | 10 | # Tools/Executables 11 | LUMO ?=lumo 12 | LUMO_BUILD_FILE ?=build.cljs 13 | NEON ?=neon 14 | NODE_GYP ?=node-gyp 15 | NPM ?=npm 16 | 17 | # Auxiliary Functions 18 | quiet = $(if $V, $1, @echo " $2"; $1) 19 | very-quiet = $(if $V, $1, @$1) 20 | 21 | #define cljscompilefn 22 | #$(shell $(LUMO) -c $1 $(LUMO_BUILD_FILE) $2) 23 | #endef 24 | 25 | ## TODO: Use vars/alias/functions from above for all build steps 26 | 27 | .DEFAULT_GOAL := local-install 28 | 29 | .PHONY : all 30 | all: local-install 31 | 32 | .PHONY : cljs-repl 33 | cljs-repl: 34 | npm run cljs-socketrepl 35 | 36 | .PHONY : cljs 37 | cljs: 38 | npm run compile-cljs 39 | 40 | .PHONY : rust 41 | rust: 42 | npm run compile-rust 43 | 44 | .PHONY : cpp 45 | cpp: 46 | npm run compile-cpp 47 | 48 | .PHONY : local-install 49 | local-install: 50 | npm install 51 | 52 | .PHONY : clean 53 | clean: 54 | rm -r ./build ./target ./native/target ./native/index.node 55 | 56 | -------------------------------------------------------------------------------- /src/daisneon/example.cljs: -------------------------------------------------------------------------------- 1 | (ns daisneon.example 2 | (:require ;[dais :as js-dais] 3 | [daisneon.dais :as dais])) 4 | 5 | ;; Note: `js-dais` is not currently used in this file, 6 | ;; but is written to correctly configure the build/compiler map, 7 | ;; so that the pattern of integrating JS sources into the CLJS compile/build 8 | ;; is established and exercised 9 | 10 | (def inter-a (dais/interceptor {:enter #(assoc % :a 1) 11 | :leave #(assoc % :leave-a 11)})) 12 | (def inter-b (dais/interceptor {:enter #(assoc % :b 2)})) 13 | (def inter-c (dais/interceptor {:enter #(assoc % :c 3)})) 14 | 15 | (def queue-chain (into cljs.core/PersistentQueue.EMPTY [inter-a 16 | inter-b 17 | inter-c])) 18 | (def array-chain #js[inter-a inter-b inter-c]) 19 | 20 | ;; For the most part, 21 | ;; these are all 0.08 - 0.3 msecs 22 | ;; Mean is probably around 0.2 msecs 23 | ;; -------------------------------- 24 | 25 | ;; 0.1 - 0.31 msecs 26 | (defn ^:export example [] 27 | (let [context {:dais/terminators [#(:b %)] 28 | :dais/queue [{:enter #(assoc % :a 1) 29 | :leave #(assoc % :leave-a 11)} 30 | {:enter #(assoc % :b 2)} 31 | {:enter #(assoc % :c 3)}]}] 32 | (dais/execute context))) 33 | 34 | ;; 0.1 - 0.30 msecs 35 | (defn ^:export example1 [] 36 | (let [context {:dais/terminators [#(:b %)] 37 | :dais/queue [inter-a 38 | inter-b 39 | inter-c]}] 40 | (dais/execute context))) 41 | 42 | ;; 0.09 - 0.29 msecs 43 | (defn ^:export example1b [] 44 | (let [context {:dais/terminators [#(:b %)] 45 | :dais/queue queue-chain}] 46 | (dais/execute context))) 47 | 48 | ;; 0.07 - 0.30 msecs 49 | (defn ^:export exampleStatic [] 50 | (let [context {:dais/terminators [#(:b %)] 51 | :dais/queue #js[{:enter #(assoc % :a 1) 52 | :leave #(assoc % :leave-a 11)} 53 | {:enter #(assoc % :b 2)} 54 | {:enter #(assoc % :c 3)}]}] 55 | (dais/execute context))) 56 | 57 | ;; 0.06 - 0.28 msecs 58 | (defn ^:export exampleStatic1 [] 59 | (let [context {:dais/terminators [#(:b %)] 60 | :dais/queue #js[inter-a 61 | inter-b 62 | inter-c]}] 63 | (dais/execute context))) 64 | 65 | ;; 0.06 - 0.26 msecs 66 | (defn ^:export exampleStatic1b [] 67 | (let [context {:dais/terminators [#(:b %)] 68 | :dais/queue array-chain}] 69 | (dais/execute context))) 70 | 71 | -------------------------------------------------------------------------------- /src/dais.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict' 3 | 4 | // The Dais interceptor chain in pure, naive JavaScript (using mutable data types) 5 | 6 | function handleLeave(context) { 7 | let stack = context["dais.stack"]; 8 | let stackLen = stack.length; 9 | for(let i = 0; i < stackLen; i++) { 10 | let interceptor = stack.pop(); 11 | try { 12 | if (interceptor.leave !== undefined) { 13 | context = interceptor.leave(context); 14 | } 15 | if (context.error !== undefined) { 16 | return handleError(context); 17 | } 18 | } catch (e) { 19 | context["error"] = e; 20 | return handleError(context); 21 | } 22 | } 23 | return context; 24 | } 25 | 26 | function handleError(context) { 27 | let stack = context["dais.stack"]; 28 | let stackLen = stack.length; 29 | for(let i = 0; i < stackLen; i++) { 30 | let error = context.error; 31 | if (error === undefined) { 32 | return handleLeave(context) 33 | } 34 | 35 | let interceptor = stack.pop(); 36 | if (interceptor.error !== undefined) { 37 | context = interceptor.error(context); 38 | } 39 | } 40 | return context; 41 | } 42 | 43 | function handleEnter(context) { 44 | let queue = context["dais.queue"]; 45 | let stack = context["dais.stack"]; 46 | let terminators = context["dais.terminators"]; 47 | while (queue.length > 0) { 48 | let interceptor = queue[0]; 49 | if (interceptor === undefined) { 50 | delete context["dais.queue"]; 51 | return handleLeave(context); 52 | } 53 | 54 | queue.shift(); 55 | stack.push(interceptor); 56 | 57 | try { 58 | if (interceptor.enter !== undefined) { 59 | context = interceptor.enter(context); 60 | } 61 | if (context.error !== undefined) { 62 | return handleError(context); 63 | } 64 | } catch (e) { 65 | context["error"] = e; 66 | return handleError(context); 67 | } 68 | if (terminators !== undefined) { 69 | if (terminators.some((elem, i, array) => elem.call(this, context))) { 70 | delete context["dais.queue"]; 71 | return handleLeave(context); 72 | } 73 | } 74 | } 75 | return context; 76 | } 77 | 78 | function execute(context, interceptors) { 79 | if (interceptors !== undefined) { 80 | context["dais.queue"] = interceptors; 81 | } 82 | context["dais.stack"] = context["dais.stack"] || []; 83 | return handleEnter(context); 84 | } 85 | 86 | module.exports.execute = execute; 87 | module.exports.handleEnter = handleEnter; 88 | module.exports.handleLeave = handleLeave; 89 | module.exports.handleError = handleError; 90 | 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Dais-Neon 3 | =========== 4 | 5 | Dais interceptor chain in Rust+Node.js with ClojureScript support. 6 | 7 | There is also C++ support via node-gyp and [Native Abstractions for Node.js](https://github.com/nodejs/nan). 8 | 9 | The Dais Interceptor Chain is a simple, synchronous-only interceptor chain, 10 | inspired by [Pedestal](https://github.com/pedestal/pedestal). 11 | [Dais](https://github.com/ohpauleez/dais) was first written in Java and 12 | designed to be used in Java and Clojure. 13 | 14 | 15 | ### Getting Started 16 | 17 | You can compile the Rust, C++, and ClojureScript with `npm install`. 18 | 19 | From there you can enter Node.js REPL with `node`: 20 | 21 | ``` 22 | $ node 23 | > const dais = require('.') 24 | > dais.hello() 25 | 'hello node - from JavaScript' 26 | > dais.hellorust() 27 | 'hello node - from Rust' 28 | > dais.hellocljs() 29 | 'hello node - from ClojureScript' 30 | > dais.hellocpp() 31 | 'hello node - from C++' 32 | ``` 33 | 34 | And you can also use the ClojureScript/Lumo REPL (without access to index.js) 35 | 36 | ``` 37 | $ make cljs-repl 38 | cljs.user=> (require '[daisneon.bridge :as bridge]) 39 | nil 40 | cljs.user=> (.hello bridge/rust) 41 | "hello node - from Rust" 42 | cljs.user=> (.Hello bridge/cpp) 43 | "hello node - from C++" 44 | ``` 45 | 46 | 47 | ### Hacking 48 | 49 | * This project uses `npm` for build tasks. 50 | For convenience, these are plumbed into a Makefile 51 | * The ClojureScript REPL has socketrepl support, all via Lumo 52 | * You may want to modify `target/main.js` and add `goog.require("daisneon.example");` 53 | if you're working at the node shell via index.js 54 | * You can directly require JS files from the CLJS REPL 55 | eg: `(require '[dais :as dais])` for JavaScript Dais chain 56 | 57 | 58 | ### Basic benchmarks 59 | 60 | This project is mostly about exploring the integration possibilities and 61 | understanding runtime overhead. The native code never migrates to typed arrays, 62 | it only does interop and works directly in the v8 types. 63 | 64 | "The machine code that V8 emits for your JS code can specialize and take 65 | shortcuts but every element Get/Set call in your [native] code goes through 66 | a lot of layers to implement the proper JS semantics." [see issue](https://github.com/nodejs/nan/issues/640) 67 | 68 | To see the general numbers, modify `target/main.js` and add `goog.require("daisneon.example");` 69 | Then you can examine chain executions: 70 | 71 | ``` 72 | $ node 73 | > const dais = require('.') 74 | > dais.testChains() 75 | ``` 76 | 77 | * rust: 0.073ms 78 | * Static processing only (no dynamic chains, and no error handling) 79 | * cpp: 0.042ms 80 | * Static processing only (no dynamic chains, and no error handling) 81 | * js: 0.051ms 82 | * cljs-static: 0.287ms 83 | * cljs: 0.346ms 84 | * cljs-js-interop: 1.737ms 85 | * Involves converting JS objects to maps and then back to objects at the end 86 | 87 | For reference, the Java Dais chain runs between 0.02ms - 0.20ms. 88 | The Pedestal Chain runs between 1-3ms. The Pedestal chain includes logging, 89 | runtime metrics, is thread-safe, and has full async capabilities 90 | (ie: it includes extra signaling and thread-pool migration) 91 | 92 | 93 | ### TODO 94 | 95 | * Consider adding Nan::TryCatch and tc.HasCaught() to the C++ code 96 | 97 | -------------------------------------------------------------------------------- /native/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate neon; 3 | extern crate itertools; 4 | 5 | use std::collections::VecDeque; 6 | use itertools::Itertools; 7 | use itertools::FoldWhile::{Continue, Done}; 8 | 9 | use neon::vm::{Call, JsResult}; 10 | use neon::js::{JsFunction, JsString, JsNumber, JsBoolean, JsNull, JsArray, JsObject, JsValue, Object}; 11 | use neon::mem::Handle; 12 | 13 | fn hello(call: Call) -> JsResult { 14 | let scope = call.scope; 15 | Ok(JsString::new(scope, "hello node - from Rust").unwrap()) 16 | } 17 | 18 | fn ret_obj(call: Call) -> JsResult { 19 | let scope = call.scope; 20 | let obj: Handle = JsObject::new(scope); 21 | obj.set("one", JsNumber::new(scope, 1_f64))?; 22 | Ok(obj) 23 | } 24 | 25 | fn basic_execute(call: Call) -> JsResult { 26 | let scope = call.scope; 27 | let context = call.arguments.require(scope, 0)?.check::()?; 28 | let mut stack : VecDeque> = VecDeque::new(); 29 | let mut did_terminate = false; 30 | let interceptors: Vec> = call.arguments.require(scope, 1)?.check::()?.to_vec(scope)?; 31 | let ectx = interceptors.iter() 32 | .fold_while(context, |ctx, interceptor| { 33 | let i: Handle = interceptor.downcast::().unwrap(); 34 | stack.push_front(i); 35 | // TODO: We need to do a `match` on enter_f in case the interceptor 36 | // doesn't have it 37 | let enter_f: Handle = i.get(scope, "enter").unwrap().downcast::().unwrap(); 38 | let new_ctx: Handle = enter_f.call(scope, JsNull::new(), vec![ctx]).unwrap().check::().unwrap(); 39 | let terminators: Vec> = ctx.get(scope, "dais.terminators").unwrap().check::().unwrap().to_vec(scope).unwrap(); 40 | let should_terminate = terminators.iter() 41 | .any(|term_val| { 42 | let terminator: Handle = term_val.downcast::().unwrap(); 43 | let term_res: Handle = terminator.call(scope, JsNull::new(), vec![ctx]).unwrap().check::().unwrap(); 44 | term_res.value()}); 45 | if should_terminate { did_terminate = true; Done(new_ctx) } else { Continue(new_ctx) }}).into_inner(); 46 | 47 | let final_ctx = if did_terminate { 48 | stack.iter() 49 | .fold(ectx, |ctx, interceptor| { 50 | let maybe_leave = interceptor.get(scope, "leave").unwrap().downcast::(); 51 | match maybe_leave { 52 | Some(leave_f) => leave_f.call(scope, JsNull::new(), vec![ctx]).unwrap().check::().unwrap(), 53 | None => ctx, 54 | }}) 55 | } else { ectx }; 56 | Ok(final_ctx) 57 | //Ok(if did_terminate { handle_leave(&call, &mut ctx, &stack) } else { ctx }) 58 | } 59 | 60 | register_module!(m, { 61 | m.export("hello", hello)?; 62 | m.export("returnObj", ret_obj)?; 63 | m.export("basicExecute", basic_execute)?; 64 | Ok(()) 65 | }); 66 | 67 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // All of these require `npm install` to work 4 | const addon = require('../native'); 5 | const cppaddon = require('../build/Release/cppaddon'); 6 | const daiscljs = require('../target/main.js'); 7 | const daisjs = require('./dais.js'); 8 | 9 | function hello() { 10 | return 'hello node - from JavaScript'; 11 | } 12 | 13 | function testChains() { 14 | console.time("rust"); 15 | let rustRes = addon.basicExecute({"dais.terminators": [ctx => ctx.b !== undefined]}, 16 | [{enter: ctx => {ctx.a = 1; return ctx}, 17 | leave: ctx => {ctx["leave-a"] = 11; return ctx}}, 18 | {enter: ctx => {ctx.b = 2; return ctx}}, 19 | {enter: ctx => {ctx.c = 3; return ctx}}]); 20 | console.timeEnd("rust"); 21 | console.time("cpp"); 22 | let cppRes = cppaddon.BasicExecute({"dais.terminators": [ctx => ctx.b !== undefined]}, 23 | [{enter: ctx => {ctx.a = 1; return ctx}, 24 | leave: ctx => {ctx["leave-a"] = 11; return ctx}}, 25 | {enter: ctx => {ctx.b = 2; return ctx}}, 26 | {enter: ctx => {ctx.c = 3; return ctx}}]); 27 | console.timeEnd("cpp"); 28 | console.time("js"); 29 | let jsRes = daisjs.execute({"dais.terminators": [ctx => ctx.b !== undefined]}, 30 | [{enter: ctx => {ctx.a = 1; return ctx}, 31 | leave: ctx => {ctx["leave-a"] = 11; return ctx}}, 32 | {enter: ctx => {ctx.b = 2; return ctx}}, 33 | {enter: ctx => {ctx.c = 3; return ctx}}]); 34 | console.timeEnd("js"); 35 | console.time("cljs-js-interop"); 36 | let cljsInterRes = daisneon.dais.execute({"dais.terminators": [ctx => cljs.core.get(ctx, "b")]}, 37 | [{enter: ctx => cljs.core.assoc(ctx, "a", 1), 38 | leave: ctx => cljs.core.assoc(ctx, "leave-a", 11)}, 39 | {enter: ctx => cljs.core.assoc(ctx, "b", 2)}, 40 | {enter: ctx => cljs.core.assoc(ctx, "c", 3)}]); 41 | console.timeEnd("cljs-js-interop"); 42 | console.time("cljs-static"); 43 | let cljsStaticRes = daisneon.example.exampleStatic1b(); 44 | console.timeEnd("cljs-static"); 45 | console.time("cljs"); 46 | let cljsRes = daisneon.example.example1b(); 47 | console.timeEnd("cljs"); 48 | let res = {"rustResult": rustRes, 49 | "cppResult": cppRes, 50 | "jsResult": jsRes, 51 | "cljsInterResult": cljsInterRes, 52 | "cljsStaticResult": cljs.core.clj__GT_js(cljsStaticRes), 53 | "cljsResult": cljs.core.clj__GT_js(cljsRes), 54 | }; 55 | return res; 56 | } 57 | 58 | function testA (x) { 59 | return x+1; 60 | } 61 | 62 | const testB = (x) => {return x+1}; 63 | 64 | function testFns() { 65 | let x = 1; 66 | console.time("funct"); 67 | testA(x); 68 | console.timeEnd("funct"); 69 | console.time("const"); 70 | testB(x); 71 | console.timeEnd("const"); 72 | console.time("funct2"); 73 | for (let i = 0; i < 10000; i++) { 74 | testA(x); 75 | } 76 | console.timeEnd("funct2"); 77 | console.time("const2"); 78 | for (let i = 0; i < 10000; i++) { 79 | testB(x); 80 | } 81 | console.timeEnd("const2"); 82 | } 83 | 84 | module.exports.hello = hello; 85 | module.exports.hellorust = addon.hello; 86 | module.exports.hellocljs = daisneon.dais.hello; 87 | module.exports.hellocpp = cppaddon.Hello; 88 | 89 | module.exports.executeChain = addon.executeChain; 90 | module.exports.testChains = testChains; 91 | 92 | module.exports.testFns = testFns; 93 | 94 | -------------------------------------------------------------------------------- /src/lib.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace v8; 8 | 9 | // NAN_METHOD is a Nan macro enabling convenient way of creating native node functions. 10 | // It takes a method's name as a param. By C++ convention, I used the Capital cased name. 11 | NAN_METHOD(Hello) { 12 | // Create an instance of V8's String type 13 | auto message = Nan::New("hello node - from C++").ToLocalChecked(); 14 | // 'info' is a macro's "implicit" parameter - it's a bridge object between C++ and JavaScript runtimes 15 | // You would use info to both extract the parameters passed to a function as well as set the return value. 16 | info.GetReturnValue().Set(message); 17 | } 18 | 19 | NAN_METHOD(BasicExecute) { 20 | auto global_js_context = Nan::GetCurrentContext()->Global(); 21 | auto context = info[0]->ToObject(); 22 | auto interceptors = info[1].As(); 23 | auto terminators_key = Nan::New("dais.terminators").ToLocalChecked(); 24 | auto js_terminators = Nan::Has(context, terminators_key).FromJust() ? Nan::Get(context, terminators_key).ToLocalChecked().As() : Nan::New(); 25 | std::vector> terminators; 26 | std::list> stack; 27 | 28 | auto enter_key = Nan::New("enter").ToLocalChecked(); 29 | auto leave_key = Nan::New("leave").ToLocalChecked(); 30 | auto should_process_leave = false; // A flag to signal if leave leave handlers should be processed 31 | 32 | //Prepare our terminators so we can iterate over them 33 | unsigned int term_len = 0; 34 | if (js_terminators->IsArray()) { 35 | term_len = js_terminators->Length(); 36 | } 37 | for (unsigned int i = 0; i < term_len; i++) { 38 | if (Nan::Has(js_terminators, i).FromJust()) { 39 | auto terminator = Nan::Get(js_terminators, i).ToLocalChecked().As(); 40 | terminators.push_back(terminator); 41 | } 42 | } 43 | 44 | // Start processing interceptors 45 | unsigned int len = 0; 46 | if (interceptors->IsArray()) { 47 | len = interceptors->Length(); 48 | } 49 | for (unsigned int i = 0; i < len; i++) { 50 | if (Nan::Has(interceptors, i).FromJust()) { 51 | // Get the interceptor object 52 | auto interceptor = Nan::Get(interceptors, i).ToLocalChecked().As(); 53 | // If the interceptor has a `leave`, add it to the stack 54 | if (Nan::Has(interceptor, leave_key).FromJust()) { 55 | stack.push_front(Nan::Get(interceptor, leave_key).ToLocalChecked().As()); 56 | } 57 | // If the interceptor has an `enter`, call it. Otherwise, move on to the next interceptor 58 | if (Nan::Has(interceptor, enter_key).FromJust()) { 59 | auto enter_fn = Nan::Get(interceptor, enter_key).ToLocalChecked().As(); 60 | v8::Local args[] = {context.As()}; 61 | context = Nan::Call(enter_fn, global_js_context, 1, args).ToLocalChecked().As(); 62 | // Check terminators 63 | if (std::any_of(terminators.cbegin(), terminators.cend(), 64 | [&](const Local& terminator) { 65 | return Nan::To(Nan::Call(terminator, global_js_context, 1, args).ToLocalChecked()).FromJust(); 66 | })) { 67 | should_process_leave = true; 68 | break; 69 | } 70 | } 71 | } 72 | } 73 | // Process Leave 74 | if (should_process_leave) { 75 | for (auto const &leave_fn : stack) { 76 | v8::Local args[] = {context.As()}; 77 | context = Nan::Call(leave_fn, global_js_context, 1, args).ToLocalChecked().As(); 78 | } 79 | } 80 | info.GetReturnValue().Set(context); 81 | } 82 | 83 | // Module initialization logic 84 | NAN_MODULE_INIT(Initialize) { 85 | // Export the `Hello` function (equivalent to `export function Hello (...)` in JS) 86 | NAN_EXPORT(target, Hello); 87 | NAN_EXPORT(target, BasicExecute); 88 | } 89 | 90 | // Create the module called "addon" and initialize it with `Initialize` function (created with NAN_MODULE_INIT macro) 91 | NODE_MODULE(cppaddon, Initialize); 92 | 93 | -------------------------------------------------------------------------------- /native/Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "daisneon" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "itertools 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "neon 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "neon-build 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 8 | ] 9 | 10 | [[package]] 11 | name = "aho-corasick" 12 | version = "0.6.3" 13 | source = "registry+https://github.com/rust-lang/crates.io-index" 14 | dependencies = [ 15 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 16 | ] 17 | 18 | [[package]] 19 | name = "cslice" 20 | version = "0.2.0" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | 23 | [[package]] 24 | name = "either" 25 | version = "1.2.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | 28 | [[package]] 29 | name = "gcc" 30 | version = "0.3.54" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | 33 | [[package]] 34 | name = "itertools" 35 | version = "0.6.5" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | dependencies = [ 38 | "either 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 39 | ] 40 | 41 | [[package]] 42 | name = "lazy_static" 43 | version = "0.2.9" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | 46 | [[package]] 47 | name = "libc" 48 | version = "0.2.31" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | 51 | [[package]] 52 | name = "memchr" 53 | version = "1.0.1" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | dependencies = [ 56 | "libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 57 | ] 58 | 59 | [[package]] 60 | name = "neon" 61 | version = "0.1.20" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | dependencies = [ 64 | "cslice 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "neon-build 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "neon-runtime 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "neon-build" 71 | version = "0.1.20" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | 74 | [[package]] 75 | name = "neon-runtime" 76 | version = "0.1.20" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | dependencies = [ 79 | "cslice 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 82 | ] 83 | 84 | [[package]] 85 | name = "regex" 86 | version = "0.2.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | dependencies = [ 89 | "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "regex-syntax" 98 | version = "0.4.1" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | 101 | [[package]] 102 | name = "thread_local" 103 | version = "0.3.4" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | dependencies = [ 106 | "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", 107 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 108 | ] 109 | 110 | [[package]] 111 | name = "unreachable" 112 | version = "1.0.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | dependencies = [ 115 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 116 | ] 117 | 118 | [[package]] 119 | name = "utf8-ranges" 120 | version = "1.0.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | 123 | [[package]] 124 | name = "void" 125 | version = "1.0.2" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | 128 | [metadata] 129 | "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" 130 | "checksum cslice 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40" 131 | "checksum either 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cbee135e9245416869bf52bd6ccc9b59e2482651510784e089b874272f02a252" 132 | "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" 133 | "checksum itertools 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3f2be4da1690a039e9ae5fd575f706a63ad5a2120f161b1d653c9da3930dd21" 134 | "checksum lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9e5e58fa1a4c3b915a561a78a22ee0cac6ab97dca2504428bc1cb074375f8d5" 135 | "checksum libc 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d1419b2939a0bc44b77feb34661583c7546b532b192feab36249ab584b86856c" 136 | "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" 137 | "checksum neon 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "993372f453e05590ecf8ce5d63ec905836e8b8c4b504c56a55ea4b31ab8d198e" 138 | "checksum neon-build 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d098a268ae7508a1196c87bf3cf0f5835247d4e1babc07ccf01b4535b74394cb" 139 | "checksum neon-runtime 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "f85b6c58cdd80613db616fe7ca8318ca6414d4af1ad49d80481a525bc1c6d905" 140 | "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b" 141 | "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" 142 | "checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" 143 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 144 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" 145 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 146 | -------------------------------------------------------------------------------- /src/daisneon/dais.cljs: -------------------------------------------------------------------------------- 1 | (ns daisneon.dais) 2 | 3 | ;; ---------- 4 | ;; This is an implementation of the Dais Interceptor Chain 5 | ;; written in ClojureScript and using ClojureScript data types (Records, keywords, persistent collections) 6 | ;; 7 | ;; There is support for static chain execution using JavaScript Arrays 8 | ;; -------------------------------------------------------------------- 9 | 10 | (enable-console-print!) 11 | 12 | (defrecord Interceptor [name enter leave error]) 13 | 14 | (extend-protocol IPrintWithWriter 15 | Interceptor 16 | (-pr-writer [i writer _] 17 | (write-all writer (str "#Interceptor{:name " (.-name i) "}")))) 18 | 19 | (defprotocol IntoInterceptor 20 | (-interceptor [t] "Given a value, produce an Interceptor Record.")) 21 | 22 | (extend-protocol IntoInterceptor 23 | PersistentHashMap 24 | (-interceptor [t] (map->Interceptor t)) 25 | PersistentArrayMap 26 | (-interceptor [t] (map->Interceptor t)) 27 | 28 | Interceptor 29 | (-interceptor [t] t) 30 | 31 | object 32 | (-interceptor [t] 33 | (->Interceptor (.-name t) (.-enter t) (.-leave t) (.-error t)))) 34 | 35 | (defn interceptor-name 36 | [n] 37 | (if-not (or (nil? n) (keyword? n)) 38 | (throw (ex-info (str "Name must be keyword or nil; Got: " (pr-str n)) {:name n})) 39 | n)) 40 | 41 | (defn interceptor? 42 | [o] 43 | (= (type o) Interceptor)) 44 | 45 | (defn valid-interceptor? 46 | [o] 47 | (if-let [int-vals (and (interceptor? o) 48 | (vals (select-keys o [:enter :leave :error])))] 49 | (and (some identity int-vals) 50 | (every? fn? (remove nil? int-vals)) 51 | (or (interceptor-name (:name o)) true) ;; Could return `nil` 52 | true) 53 | false)) 54 | 55 | (defn interceptor 56 | "Given a value, produces and returns an Interceptor (Record)." 57 | [t] 58 | {:pre [(if-not (satisfies? IntoInterceptor t) 59 | (throw (ex-info "You're trying to use something as an interceptor that isn't supported by the protocol; Perhaps you need to extend it?" 60 | {:t t 61 | :type (type t)})) 62 | true)] 63 | :post [(valid-interceptor? %)]} 64 | (-interceptor t)) 65 | 66 | (declare handle-leave) 67 | (declare handle-error) 68 | 69 | (defn handle-enter [context] 70 | (loop [ctx context] 71 | (if (empty? (:dais/queue ctx)) 72 | ctx 73 | (let [{queue :dais/queue 74 | stack :dais/stack} ctx 75 | interceptor (peek queue) 76 | old-context ctx 77 | new-queue (pop queue) 78 | ;; conj on nil returns a list, acts like a stack: 79 | new-stack (conj stack interceptor) 80 | partial-ctx (assoc ctx 81 | :dais/queue new-queue 82 | :dais/stack new-stack) 83 | enter-fn (:enter interceptor) 84 | new-context (if (fn? enter-fn) 85 | (try 86 | (enter-fn partial-ctx) 87 | (catch :default e 88 | (assoc (dissoc partial-ctx :dais/queue) 89 | :error e))) 90 | partial-ctx)] 91 | 92 | (if (:error new-context) 93 | (handle-error new-context) 94 | (recur (if (some #(% new-context) (:dais/terminators new-context)) 95 | (handle-leave (dissoc new-context :dais/queue)) 96 | new-context))))))) 97 | 98 | (defn handle-array-enter [context] 99 | (reduce 100 | (fn [ctx interceptor] 101 | (let [partial-ctx (update-in ctx [:dais/stack] conj interceptor) 102 | enter-fn (:enter interceptor) 103 | new-context (if (fn? enter-fn) 104 | (try 105 | (enter-fn partial-ctx) 106 | (catch :default e 107 | (assoc (dissoc partial-ctx :dais/queue) 108 | :error e))) 109 | partial-ctx)] 110 | (if (:error new-context) 111 | (reduced (handle-error new-context)) 112 | (if (some #(% new-context) (:dais/terminators new-context)) 113 | (reduced (handle-leave (dissoc new-context :dais/queue))) 114 | new-context)))) 115 | context 116 | (:dais/queue context))) 117 | 118 | (defn handle-leave [context] 119 | (loop [ctx context] 120 | (if (empty? (:dais/stack ctx)) 121 | (dissoc ctx 122 | :dais/stack 123 | "dais.stack") 124 | (let [stack (:dais/stack ctx) 125 | interceptor (peek stack) 126 | old-context ctx 127 | new-stack (pop stack) 128 | partial-ctx (assoc ctx 129 | :dais/stack new-stack) 130 | leave-fn (:leave interceptor) 131 | new-context (if (fn? leave-fn) 132 | (try 133 | (leave-fn partial-ctx) 134 | (catch :default e 135 | (assoc (dissoc partial-ctx :dais/queue) 136 | :error e))) 137 | partial-ctx)] 138 | 139 | (if (:error new-context) 140 | (handle-error new-context) 141 | (recur new-context)))))) 142 | 143 | (defn handle-error [context] 144 | (loop [ctx context] 145 | (if (empty? (:dais/stack ctx)) 146 | ctx 147 | (let [stack (:dais/stack ctx) 148 | interceptor (peek stack) 149 | old-context ctx 150 | new-stack (pop stack) 151 | partial-ctx (assoc ctx 152 | :dais/stack new-stack) 153 | error-fn (:error interceptor) 154 | new-context (if (fn? error-fn) 155 | (error-fn partial-ctx) 156 | partial-ctx)] 157 | 158 | (if (:error new-context) 159 | (recur new-context) 160 | (handle-leave new-context)))))) 161 | 162 | (defn ^:export execute 163 | ([context] 164 | {:pre [(map? context)]} 165 | (let [{q :dais/queue 166 | stack :dais/stack 167 | terminators :dais/terminators 168 | :or {stack '() 169 | terminators []}} context 170 | ;; Check for interop'd stack and terminators 171 | terminators (get context "dais.terminators" terminators) 172 | stack (get context "dais.stack" stack)] 173 | (cond 174 | (array? q) (handle-array-enter context) 175 | (seq q) (handle-enter (assoc context 176 | :dais/queue (if (instance? cljs.core/PersistentQueue q) 177 | q 178 | (into cljs.core/PersistentQueue.EMPTY q)) 179 | ;; Defend against stack types and interop 180 | :dais/stack (into '() (reverse stack)) 181 | :dais/terminators terminators)) 182 | :else context))) 183 | ([context interceptors] 184 | (let [final-ctx (execute (assoc (js->clj context) 185 | :dais/queue (into cljs.core/PersistentQueue.EMPTY 186 | (map interceptor interceptors))))] 187 | (if (object? context) 188 | (clj->js final-ctx) 189 | final-ctx)))) 190 | 191 | (defn ^:export hello [& args] 192 | "hello node - from ClojureScript") 193 | 194 | ;(println (main)) 195 | 196 | --------------------------------------------------------------------------------