{"id":13442325,"url":"https://github.com/babashka/sci","last_synced_at":"2025-12-12T01:16:47.559Z","repository":{"id":36997434,"uuid":"201774594","full_name":"babashka/sci","owner":"babashka","description":"Configurable Clojure/Script interpreter suitable for scripting and Clojure DSLs","archived":false,"fork":false,"pushed_at":"2025-04-22T10:19:07.000Z","size":3058,"stargazers_count":1265,"open_issues_count":41,"forks_count":89,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-04-23T20:59:07.249Z","etag":null,"topics":["babashka","clojure","clojurescript","graalvm","interpreter","javascript","language"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/babashka.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"borkdude","patreon":null,"open_collective":"babashka","ko_fi":"borkdude","tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2019-08-11T14:23:42.000Z","updated_at":"2025-04-21T07:19:11.000Z","dependencies_parsed_at":"2023-11-12T11:27:51.984Z","dependency_job_id":"189642d4-9875-46e4-aa4b-fb7f1b43c5cb","html_url":"https://github.com/babashka/sci","commit_stats":{"total_commits":1654,"total_committers":52,"mean_commits":"31.807692307692307","dds":0.05864570737605801,"last_synced_commit":"6d9380f55b3038769d5856fc67e7d7939ac981ef"},"previous_names":["borkdude/sci"],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fsci","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fsci/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fsci/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fsci/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/babashka","download_url":"https://codeload.github.com/babashka/sci/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250514767,"owners_count":21443208,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["babashka","clojure","clojurescript","graalvm","interpreter","javascript","language"],"created_at":"2024-07-31T03:01:44.356Z","updated_at":"2025-12-12T01:16:47.517Z","avatar_url":"https://github.com/babashka.png","language":"Clojure","funding_links":["https://github.com/sponsors/borkdude","https://opencollective.com/babashka","https://ko-fi.com/borkdude"],"categories":["Clojure","clojure"],"sub_categories":[],"readme":"\u003cimg src=\"logo/logo-300dpi.png\" width=\"100px\"\u003e\n\n[![CircleCI](https://circleci.com/gh/babashka/sci/tree/master.svg?style=shield)](https://circleci.com/gh/babashka/sci/tree/master)\n[![Clojars Project](https://img.shields.io/clojars/v/org.babashka/sci.svg)](https://clojars.org/org.babashka/sci)\n[![Financial Contributors on Open Collective](https://opencollective.com/babashka/all/badge.svg?label=financial+contributors)](https://opencollective.com/babashka)\n[![project chat](https://img.shields.io/badge/slack-join_chat-brightgreen.svg)](https://app.slack.com/client/T03RZGPFR/C015LCR9MHD)\n\n**Small Clojure Interpreter**\n\n\u003cblockquote class=\"twitter-tweet\" data-lang=\"en\"\u003e\n    \u003cp lang=\"en\" dir=\"ltr\"\u003eI want a limited dialect of Clojure for a single-purpose, scripted application. SCI will fit nicely.\u003c/p\u003e\n    \u0026mdash;\n    \u003ca href=\"https://twitter.com/tiagoluchini/status/1193144124142211073\"\u003e@tiagoluchini\u003c/a\u003e\n\u003c/blockquote\u003e\n\n## Quickstart\n\n### Use from Clojure(Script)\n\n``` clojure\n(require '[sci.core :as sci])\n(sci/eval-string \"(inc 1)\") =\u003e ;; 2\n(sci/eval-string \"(inc x)\" {:namespaces {'user {'x 2}}}) ;;=\u003e 3\n```\n\nTry SCI in your browser at [NextJournal](https://nextjournal.github.io/clojure-mode/).\n\nFor usage with GraalVM `native-image` check [here](#graalvm).\n\n## Why\n\nYou want to evaluate code from user input, or use Clojure for a DSL inside your\nproject, but `eval` isn't safe or simply doesn't work.\n\nThis library works with:\n\n- Clojure on the JVM\n- Clojure compiled with GraalVM native\n- ClojureScript, even when compiled with `:advanced`, and JavaScript\n\n## API docs\n\nSee [API.md](API.md).\n\n## Projects using SCI\n\nSCI is used in:\n\n- [Babashka](https://github.com/babashka/babashka). A Clojure scripting tool that plays well with Bash.\n- [nbb](https://github.com/babashka/nbb). Ad-hoc CLJS scripting on Node.js. (Node.js babashka)\n- [scittle](https://github.com/babashka/scittle). Execute Clojure(Script) directly from browser script tags\n- [Clerk](https://github.com/nextjournal/clerk). Local-First Notebooks for Clojure.\n- [4ever-clojure](https://4clojure.oxal.org/). 4clojure as a static web page.\n- [Clj-kondo](https://github.com/borkdude/clj-kondo/). A Clojure linter that sparks joy.\n- [Jet](https://github.com/borkdude/jet). CLI to convert between JSON, EDN and Transit.\n- [Joyride](https://github.com/BetterThanTomorrow/joyride). Making VS Code Hackable since 2022.\n- [Portal](https://github.com/djblue/portal). A clojure tool to navigate through your data.\n- [Zprint](https://github.com/kkinnear/zprint). Tool to beautifully format Clojure(script) code and data.\n- [TryClojure.org](https://tryclojure.org/). Try Clojure!\n- [SICMUtils](https://github.com/littleredcomputer/sicmutils). Computer Algebra System in Clojure, tailored for math and physics investigations.\n- [Maria.cloud](https://2.maria.cloud/): a Clojure coding environment for beginners.\n- [Overarch](https://github.com/soulspace-org/overarch): A data driven description of software architecture based on UML and the C4 model.\n\n\u003cdetails\u003e\n\u003csummary\u003eExpand for more projects...\u003c/summary\u003e\n\n- [Bootleg](https://github.com/retrogradeorbit/bootleg). An HTML templating CLI.\n- [Bytefield-svg](https://github.com/Deep-Symmetry/bytefield-svg). NodeJS library to generate byte field diagrams.\n- [Cardigan Bay](https://github.com/interstar/cardigan-bay). Wiki engine in Clojure.\n- [clj-browser-eval](https://github.com/NickCellino/clj-browser-eval). Turn any HTML input field into a Clojure interpreter.\n- [ClojureBlocks](https://codeberg.org/jhandke/ClojureBlocks), A visual editor for Clojure\n- [Chlorine](https://github.com/mauricioszabo/atom-chlorine). Socket-REPL and nREPL package for Atom editor.\n- [Cq](https://github.com/markus-wa/cq). Clojure Command-line Data Processor for JSON, YAML, EDN, XML and more.\n- [Dad](https://github.com/liquidz/dad). A configuration management tool.\n- [Datalevin](https://github.com/juji-io/datalevin). Durable Datalog database.\n- [For-science](https://github.com/pmonks/for-science). Discord bot.\n- [Keycloak-clojure](https://github.com/jgrodziski/keycloak-clojure). Clojure library for Keycloak.\n- [Lighthouse](https://github.com/barracudanetworks/lighthouse). A data-driven Kubernetes pre-processor.\n- [Logseq](https://logseq.com). A local-only outliner notebook which supports both Markdown and Org mode.\n- [Malli](https://github.com/metosin/malli). Plain data Schemas for Clojure/Script.\n- [Obsidian Wielder](https://github.com/victorb/obsidian-wielder). Write and evaluate Clojure code directly in your Obsidian vault.\n- [PCP](https://github.com/alekcz/pcp). Clojure Processor (PHP replacement).\n- [PGMig](https://github.com/leafclick/pgmig). Fast Standalone PostgreSQL Migration Runner.\n- [Prose](https://github.com/JeremS/prose). Alternate syntax for Clojure, similar to what Pollen brings to Racket.\n- [Spire](https://github.com/epiccastle/spire). Pragmatic provisioning using Clojure.\n- [Tesserae](https://github.com/lumberdev/tesserae). A Clojure spreadsheet and more!\n\n\u003c/details\u003e\n\nAre you using SCI in your company or projects? Let us know [here](https://github.com/babashka/sci/discussions/662).\n\n## Installation\n\nUse as a dependency:\n\n[![Clojars Project](https://img.shields.io/clojars/v/org.babashka/sci.svg)](https://clojars.org/org.babashka/sci)\n\n## Usage\n\nThe main API function is `sci.core/eval-string` which takes a string to evaluate\nand an optional options map.\n\nIn SCI, `defn` does not mutate the outside world, only the evaluation\ncontext inside a call to `sci/eval-string`.\n\nBy default SCI only enables access to most of the Clojure core functions. More\nfunctions can be enabled by using `:namespaces` and `:classes`. Normally you\nwould use SCI's version of `println` but here, for the purposes of\ndemonstration, we use Clojure's version of `println` instead:\n\n``` clojure\nuser=\u003e (require '[sci.core :as sci])\nuser=\u003e (sci/eval-string \"(println \\\"hello\\\")\" {:namespaces {'clojure.core {'println println}}})\nhello\nnil\n```\n\nIt is also possible to provide namespaces which can be required inside a SCI program:\n\n``` clojure\nuser=\u003e (def opts {:namespaces {'foo.bar {'println println}}})\nuser=\u003e (sci/eval-string \"(require '[foo.bar :as lib]) (lib/println \\\"hello\\\")\" opts)\nhello\nnil\n```\n\nYou can provide a list of allowed symbols. Using other symbols causes an exception:\n\n``` clojure\nuser=\u003e (sci/eval-string \"(inc 1)\" {:allow '[inc]})\n2\nuser=\u003e (sci/eval-string \"(dec 1)\" {:allow '[inc]})\nExceptionInfo dec is not allowed! [at line 1, column 2]  clojure.core/ex-info (core.clj:4739)\n```\n\nProviding a list of disallowed symbols has the opposite effect:\n\n``` clojure\nuser=\u003e (sci/eval-string \"(inc 1)\" {:deny '[inc]})\nExceptionInfo inc is not allowed! [at line 1, column 2]  clojure.core/ex-info (core.clj:4739)\n```\n\n### Macros\n\nProviding a macro as a binding can be done by providing a normal function that:\n- has `:sci/macro` on the metadata set to `true`\n- has two extra arguments at the start for `\u0026form` and `\u0026env`:\n\n``` clojure\nuser=\u003e (def do-twice ^:sci/macro (fn [_\u0026form _\u0026env x] (list 'do x x)))\nuser=\u003e (sci/eval-string \"(do-twice (f))\" {:bindings {'do-twice do-twice 'f #(println \"hello\")}})\nhello\nhello\nnil\n```\n\nAlternatively you can refer to the macro from the Clojure environment via the\nvar (this only works in a JVM environment):\n\n``` clojure\nuser=\u003e (defmacro do-twice [x] (list 'do x x))\nuser=\u003e (sci/eval-string \"(do-twice (f))\" {:namespaces {'user {'do-twice #'do-twice 'f #(println \"hello\")}}})\n```\n\nTips:\n\n* To get the name of the namespace the macro is called from at _expansion_ time, use `(str (deref sci.core/ns))`\n* Have a look at [Emmy's sci-macro](https://github.com/mentat-collective/emmy/blob/e16b5692b04972f0bc9ea6d07f7ead41edccc066/src/emmy/util.cljc#L141), which behaves like `defmacro` in Clojure, but emits a `:sci/macro` defn with the extra args in cljs\n\n### Vars\n\nTo remain safe and sandboxed, SCI programs do not have access to Clojure vars,\nunless you explicitly provide that access. SCI has its own var type,\ndistinguished from Clojure vars.\n\nIn a SCI program these vars are created with `def` and `defn` just like in\nnormal Clojure:\n\n``` clojure\n(def x 1)\n(defn foo [] x)\n(foo) ;;=\u003e 1\n(def x 2)\n(foo) ;;=\u003e 2\n```\n\nDynamic vars with thread-local bindings are also supported (for vars defined _inside_ your scripts, and thus _evaluated_ by SCI,\nor for SCI dynamic vars (see below)):\n\n``` clojure\n(def ^:dynamic *x* 1)\n(binding [*x* 10] *x*) ;;=\u003e 10\n(binding [*x* 10] (set! *x* 12) *x*) ;;=\u003e 12\n*x* ;;=\u003e 1\n```\n\nCreating SCI vars _from Clojure_, to be exposed to your SCI scipts, can be done using `sci/new-var`:\n\n``` clojure\n(def x (sci/new-var 'x 10))\n(sci/eval-string \"(inc x)\" {:namespaces {'user {'x x}}}) ;;=\u003e 11\n```\n\nTo create a dynamic SCI var from Clojure you can set metadata or use `sci/new-dynamic-var`:\n\n``` clojure\n(def x1 (sci/new-var 'x 10 {:dynamic true}))\n(sci/eval-string \"(binding [*x* 12] (inc *x*))\" {:namespaces {'user {'*x* x1}}}) ;;=\u003e 13\n(def x2 (sci/new-dynamic-var 'x 10))\n(sci/eval-string \"(binding [*x* 12] (inc *x*))\" {:namespaces {'user {'*x* x2}}}) ;;=\u003e 13\n```\n\nThese dynamic SCI vars can be bound from Clojure using `sci/binding`:\n\n``` clojure\n(def x (sci/new-dynamic-var 'x 10))\n(sci/binding [x 11] (sci/eval-string \"(inc *x*)\" {:namespaces {'user {'*x* x}}})) ;;=\u003e 12\n```\n\nNotice that you cannot set _host_ dynamic variables _from your SCI scripts_ - `binding` will only work\non dynamic variables you defined in the script itself, or on SCI dynamic variables exposed from Clojure.\nThis applies also to :sci/macros (which expand into more code in the scripts). There workaround is to\nonly bind them from Clojure, i.e. from functions exposed to and _called by_ your scripts:\n\n``` clojure\n(def ^:dynamic *x* 1)\n(defn with-x [x-val f] (binding [*x* x-val] (f)))\n(defn get-x [] *x*)\n(def userns (sci/create-ns 'user))\n(sci/eval-string \"(with-x 42 #(get-x))\"\n                 {:namespaces {'user {'with-x (sci/copy-var with-x userns)\n                                      'get-x (sci/copy-var get-x userns)}}})\n;;=\u003e 42\n```\n\nIf you want to be bind the value from your script, then you can expose a SCI dynamic var to it,\nand bind its value to the host dynamic var in Clojure:\n\n```clj\n(def ^:dynamic *x* 1)\n(def userns (sci/create-ns 'user))\n(def sci-x (sci/copy-var *x* userns)) ; ^:dynamic is copied too\n(defn get-x [] (binding [*x* @sci-x] *x*)) ; bind host var to SCI dyn var value\n(sci/eval-string \"(binding [*x* 42] (get-x))\"\n                 {:namespaces {'user {'get-x (sci/copy-var get-x userns)\n                                      '*x* sci-x}}}) ; expose SCI dyn var\n;; =\u003e 42\n```\n\n#### Using `*in*`, `*out*`, `*err*`\n\nThe dynamic vars `*in*`, `*out*`, `*err*` in a SCI program correspond to the\ndynamic SCI vars `sci/in`, `sci/out` and `sci/err` in the API. These\nvars can be rebound as well:\n\n``` clojure\n(def sw (java.io.StringWriter.))\n(sci/binding [sci/out sw] (sci/eval-string \"(println \\\"hello\\\")\")) ;;=\u003e nil\n(str sw) ;;=\u003e \"hello\\n\"\n```\n\nA shorthand for rebinding `sci/out` is `sci/with-out-str`:\n\n``` clojure\n(sci/with-out-str (sci/eval-string \"(println \\\"hello\\\")\")) ;;=\u003e \"hello\\n\"\n```\n### Reader conditionals\n\nTo tell SCI which branch of a [reader conditional](https://clojure.org/guides/reader_conditionals) to take,\nsuch as the `:cljs` one in `#?(:clj \"JVM\" :cljs \"JS\")`, you need to tell it which _features_ it should support:\n\n```clojure\n(sci/eval-string \"(str \\\"I'm \\\" #?(:clj \\\"JVM\\\" :cljs \\\"JS\\\"))\" {:features #{:cljs :bb}}) ;;=\u003e \"I'm JS\"\n```\n\nSee [eval-string docs](https://github.com/babashka/sci/blob/master/API.md#sci.core/eval-string) for details.\n\n### Copy a namespace\n\nTo copy the public vars of a Clojure namespace and to reify the Clojure vars into\ncorresponding SCI vars, you can use `ns-publics` in Clojure and the following API functions:\n\n- [`sci/create-ns`](https://github.com/babashka/sci/blob/master/API.md#create-ns)\n- [`sci/copy-var`](https://github.com/babashka/sci/blob/master/API.md#copy-var)\n- [`sci/copy-var*`](https://github.com/babashka/sci/blob/master/API.md#copy-var-1)\n- [`sci/copy-ns`](https://github.com/babashka/sci/blob/master/API.md#copy-ns)\n\nE.g. given the following Clojure namespace:\n\n``` clojure\n(ns foobar)\n\n(defmacro do-twice [x] (list 'do x x))\n\n(defn times-two [x]\n  (* x 2))\n\n(defn silly-name [x] (* x x))\n```\n\nyou can re-create that namespace in a SCI context like this:\n\n``` clojure\n(require 'foobar)\n\n(def fns (sci/create-ns 'foobar-ns nil))\n\n(def foobar-ns {'do-twice (sci/copy-var foobar/do-twice fns)\n                'times-two (sci/copy-var foobar/times-two fns)\n                'better-name (sci/copy-var foobar/silly-name fns {:name 'better-name})})\n\n(def ctx (sci/init {:namespaces {'foobar foobar-ns}}))\n\n(sci/binding [sci/out *out*]\n  (sci/eval-string* ctx \"(foobar/do-twice (prn :x))\"))\n:x\n:x\nnil\n\n(sci/eval-string* ctx \"(foobar/times-two 2)\")\n4\n\n(sci/eval-string* ctx \"(foobar/better-name 4)\")\n16\n```\n\nTo copy an entire namespace without enumerating all vars explicitly with\n`sci/copy-var` you can use the following approach using `ns-publics` and\n`sci/copy-var*`, which works the same in Clojure and ClojureScript:\n\n``` Clojure\n(let [ens (sci/create-ns 'edamame.core)\n      publics (ns-publics 'edamame.core)\n      sci-ns (update-vals publics #(sci/copy-var* % ens))\n      ctx (sci/init {:namespaces {'edamame.core sci-ns}})]\n  (prn (sci/eval-string* ctx \"(require '[edamame.core :as e]) (e/parse-string \\\"1\\\")\"))\n  ;;=\u003e 1\n  )\n```\n\nBecause part of copying of the namespace could be done at compile time, which in\nClojureScript has the benefit that some vars are not part of the compiled output\nand may result in smaller JS output, there is also the `sci/copy-ns` _macro_\nwhich allows you to exclude vars at compile-time:\n\n``` Clojure\n(let [ens (sci/create-ns 'edamame.core)\n      sci-ns (sci/copy-ns edamame.core ens {:exclude [iobj?]})\n      ctx (sci/init {:namespaces {'edamame.core sci-ns}})]\n  (prn (sci/eval-string* ctx \"(require '[edamame.core :as e]) (e/parse-string \\\"1\\\")\"))\n  ;;=\u003e 1\n  )\n```\n\n### Stdout and stdin\n\n#### Clojure\n\nTo enable printing to `stdout` and reading from `stdin` you can SCI-bind\n`sci/out` and `sci/in` to `*out*` and `*in*` respectively:\n\n\n``` clojure\n(sci/binding [sci/out *out*\n              sci/in *in*]\n  (sci/eval-string \"(print \\\"Type your name!\\n\u003e \\\")\")\n  (sci/eval-string \"(flush)\")\n  (let [name (sci/eval-string \"(read-line)\")]\n    (sci/eval-string \"(printf \\\"Hello %s!\\\" name)\n                      (flush)\"\n                     {:bindings {'name name}})))\nType your name!\n\u003e Michiel\nHello Michiel!\n```\n\nWhen adding a Clojure function to SCI that interacts with `*out*` (or `*in*` or `*err*`), you\ncan hook it up to SCI's context. For example, a Clojure function that writes to `*out*`\ncan be Clojure bound to SCI's `out`:\n\n```Clojure\nuser=\u003e (defn foo [] (println \"yello!\"))\n#'user/foo\nuser=\u003e ;; without binding *out* to sci's out, the Clojure function will use its default *out*:\nuser=\u003e (sci/eval-string \"(with-out-str (foo))\" {:namespaces {'user {'foo foo}}})\nyello!\n\"\"\n;; Let's hook foo up to SCI's context:\nuser=\u003e (defn wrapped-foo [] (binding [*out* @sci/out] (foo)))\n#'user/wrapped-foo\nuser=\u003e (sci/eval-string \"(with-out-str (foo))\" {:bindings {'foo wrapped-foo}})\n\"yello!\\n\"\n```\n\nTo always enable printing in your SCI environment you can set `sci/out` and `sci/err` to `*out*` and `*err*` respectively, globally:\n\n``` Clojure\n(sci/alter-var-root sci/out (constantly *out*))\n(sci/alter-var-root sci/err (constantly *err*))\n```\n\n#### ClojureScript\n\nSimilar to Clojure vs. CLJS, the difference with SCI on Clojure vs. SCI on CLJS\nis that in the latter you should use `sci/print-newline` and `sci/print-fn` to\ncontrol printing to stdout:\n\n``` Clojure\ncljs.user=\u003e (def output (atom \"\"))\n#'cljs.user/output\ncljs.user=\u003e (sci/binding [sci/print-newline true sci/print-fn (fn [s] (swap! output str s))] (sci/eval-string \"(print :hello) (println :bye)\"))\nnil\ncljs.user=\u003e @output\n\":hello:bye\\n\"\n```\n\nThis is supported since SCI 0.2.7.\n\nTo always enable printing in your SCI environment you can set `sci/print-fn` to `*print-fn*` globally:\n\n``` Clojure\n(enable-console-print!)\n(sci/alter-var-root sci/print-fn (constantly *print-fn*))\n(sci/alter-var-root sci/print-err-fn (constantly *print-err-fn*))\n```\n\nIf you are seeing the error _Attempting to call unbound fn: #'clojure.core/*print-fn*_ then this should fix it.\n\n### Futures\n\nCreating threads with `future` and `pmap` is disabled by default, but can be\nenabled by requiring `sci.addons.future` and applying the `sci.addons.future/install` function\nto the SCI options:\n\n``` clojure\n(ns my.sci.app\n  (:require\n   [sci.core :as sci]\n   [sci.addons.future :as future]))\n\n(sci/eval-string \"@(future (inc x))\"\n                 (-\u003e {:namespaces {'user {'x 1}}}\n                     (future/install)))\n;;=\u003e 2\n```\n\nFor conveying thread-local SCI bindings to an external `future` use\n`sci/future`:\n\n``` clojure\n(ns my.sci.app\n  (:require\n   [sci.core :as sci]\n   [sci.addons.future :as future]))\n\n(def x (sci/new-dynamic-var 'x 10))\n\n@(sci/binding [x 11]\n   (sci/future\n     (sci/eval-string \"@(future (inc x))\"\n                      (-\u003e {:namespaces {'user {'x x}}}\n                          (future/install)))))\n;;=\u003e 12\n```\n\n### Classes\n\nAdding support for classes is done via the `:classes` option:\n\n``` clojure\n(sci/eval-string \"(java.util.UUID/randomUUID)\"\n  {:classes {'java.util.UUID java.util.UUID}})\n;;=\u003e #uuid \"312ba519-37e2-4109-b164-97fb140b57b0\"\n```\n\nTo make this work with `GraalVM` you will also need to add an entry to your\n[reflection\nconfig](https://www.graalvm.org/latest/reference-manual/native-image/dynamic-features/Reflection/)\nfor this class. Also see [`reflection.json`](reflection.json).\n\nBy default, SCI only lets you interop with classes explicitly provided in the\n`:classes` config. When a method call returns an instance of a class that is not\nin `:classes` you won't be able to interop on that. You can disable this safety\nmeasure with `{:classes {:allow :all}}`.\n\nIn JS hosts, to allow interop with anything, use the following config:\n\n``` clojure\n{:classes {'js js/globalThis :allow :all}}\n```\n\nNote that the value for`'js`, `js/globalThis` is just a JavaScript object. To\ncontrol in a more fine-grained manner what \"classes\" are available in a JS\nenvironment, just limit the keys to the ones you would like to expose:\n\n``` Clojure\n{:classes {'js #js {:Promise js/Promise} :allow :all}}\n```\n\nThe `:allow :all` option takes care that everything reachable via `:classes` is\nallowed to be used, it does not mean that you have access to all classes in the\nhost environment.\n\n### JavaScript libraries\n\nAdding support for JavaScript libraries is done via the `:js-libs` option:\n\n```clojure\n(ns sci.examples.js-libs\n  (:require [\"fs\" :as fs]\n            [sci.core :as sci]))\n\n(sci/eval-string \"\n(require '[\\\"fs\\\" :as fs])\n(fs/existsSync \\\"README.md\\\")\"\n                 {:js-libs {\"fs\" fs}})\n;;=\u003e true\n```\n\nNote that JavaScript libraries _must_ be required using a string library name.\n\n[Property notation](https://clojurescript.org/news/2021-04-06-release#_library_property_namespaces) is also supported:\n\n``` clojure\n(require '[\"fs$readFileSync\" :as slurp])\n(slurp \"README.md\" \"utf-8\")\n```\n\nJavaScript libraries can be added to an existing SCI context using `sci/add-js-lib!`.\n\n### State\n\nSCI uses a context (internally implemented using an atom) to keep track of state\nchanges like newly defined namespaces and vars. The contents of the context\nshould be considered implementation detail. Every call to `eval-string` creates\na fresh context. To preserve state over multiple evaluations, you can create a\ncontext using the same options as those for `sci/eval-string`.\n\n``` clojure\n(def opts {:namespaces {'foo.bar {'x 1}}})\n(def sci-ctx (sci/init opts))\n```\n\nThe SCI context can then be re-used over successive invocations of\n`sci/eval-string*`:\n\n``` clojure\n(sci/eval-string* sci-ctx \"foo.bar/x\") ;;=\u003e 1\n(sci/eval-string* sci-ctx \"(ns foo.bar) (def x 2) x\") ;;=\u003e 2\n(sci/eval-string* sci-ctx \"foo.bar/x\") ;;=\u003e 2\n```\n\nIn a multi-user environment it can be useful to give each user their own\ncontext. This can already be achieved with `eval-string`, but for performance\nreasons it may be desirable to initialize a shared context once. This shared\ncontext can then be forked for each user so that changes in one user's context\naren't visible to other users:\n\n``` clojure\n(def forked (sci/fork sci-ctx))\n(sci/eval-string* forked \"(def forked 1)\")\n(sci/eval-string* forked \"forked\") ;;=\u003e 1\n(sci/eval-string* sci-ctx \"forked\") ;;=\u003e Could not resolved symbol: forked\n```\n\n### Implementing require and load-file\n\nSCI supports loading code via a hook that is invoked by SCI's implementation of\n`require`. The job of this function is to find and return the source code for\nthe requested namespace. This passed-in function will be called with a single\nargument that is a hashmap with a key `:namespace`. The value for this key will\nbe the _symbol_ of the requested namespace.\n\nThis function should return a map with keys `:file` (containing the filename to\nbe used in error messages) and `:source` (containing the source code text). SCI\nwill evaluate that source code to satisfy the call to `require`. Alternatively\nthe function can return `nil` which will result in SCI throwing an exception\nthat the namespace could not be found.\n\nThe load hook is passed as part of the SCI options via the `:load-fn`:\n\n``` clojure\n(defn load-fn [{:keys [namespace]}]\n  (when (= namespace 'foo)\n    {:file \"foo.clj\"\n     :source \"(ns foo) (def val :foo)\"}))\n(sci/eval-string \"(require '[foo :as fu]) fu/val\" {:load-fn load-fn})\n;;=\u003e :foo\n```\n\nNote that internally specified namespaces (either the default namespaces that\nSCI provides itself or those provides via the `:namespaces` key) will be\nconsidered first and if found there, `:load-fn` will not be called, unless\n`:reload` or `:reload-all` are used:\n\n``` clojure\n(sci/eval-string\n  \"(require '[foo :as fu])\n   fu/val\"\n  {:load-fn load-fn\n   :namespaces {'foo {'val (sci/new-var 'val :internal)}}})\n;;=\u003e :internal\n\n(sci/eval-string\n  \"(require '[foo :as fu] :reload)\n   fu/val\"\n  {:load-fn load-fn\n   :namespaces {'foo {'val (sci/new-var 'val :internal)}}})\n;;=\u003e :foo\n```\n\nAnother option for loading code is to provide an implementation of\n`clojure.core/load-file`. An example is presented here.\n\n``` clojure\n(ns my.sci.app\n    (:require [sci.core :as sci]\n              [clojure.java.io :as io]))\n\n(spit \"example1.clj\" \"(defn foo [] :foo)\")\n(spit \"example2.clj\" \"(load-file \\\"example1.clj\\\")\")\n\n(let [load-file (fn [file]\n                  (let [file (io/file file)\n                        source (slurp file)]\n                    (sci/with-bindings\n                      {sci/ns @sci/ns\n                       sci/file (.getAbsolutePath file)}\n                      (sci/eval-string source opts))))\n      opts {:namespaces {'clojure.core {'load-file load-file}}}]\n  (sci/eval-string \"(load-file \\\"example2.clj\\\") (foo)\" opts))\n;;=\u003e :foo\n```\n\n## REPL\n\nImplementing a REPL can be done using the following functions:\n\n- `sci/reader`: returns reader for parsing source code, either from a string or `io/reader`\n- `sci/parse-next`: returns next form from reader\n- `sci/eval-form`: evaluates form returned by `parse-next`.\n\nSee [examples](examples/sci/examples) for examples for both Clojure and ClojureScript.\nRun instructions are included at the bottom of each example.\n\nTo include an nREPL server in your sci-based project, you can use\n[babashka.nrepl](https://github.com/babashka/babashka.nrepl).\n\n## GraalVM\n\nFor general information about Clojure and GraalVM, check out\n[clj-graal-docs](https://github.com/lread/clj-graal-docs) and\n[graalvm-clojure](https://github.com/BrunoBonacci/graalvm-clojure/).\n\n### Clojure version\n\nTo build native images with GraalVM it is recommended to use Clojure `1.10.3` or\nlater.\n\n\u003c!-- ## Use from JavaScript --\u003e\n\n\u003c!-- Sci is available on NPM: --\u003e\n\n\u003c!-- ``` shell --\u003e\n\u003c!-- $ npm install @borkdude/sci --\u003e\n\u003c!-- ``` --\u003e\n\n\u003c!-- The JavaScript API consists of two functions, `evalString` to evaluate Clojure --\u003e\n\u003c!-- expressions and `toJS` to convert Clojure data structures back to JavaScript. --\u003e\n\n\u003c!-- ``` javascript --\u003e\n\u003c!-- \u003e const { evalString, toJS } = require('@borkdude/sci'); --\u003e\n\u003c!-- \u003e x = evalString(\"(assoc {:a 1} :b 2)\") --\u003e\n\u003c!-- \u003e toJS(x) --\u003e\n\u003c!-- { a: 1, b: 2 } --\u003e\n\u003c!-- ``` --\u003e\n\n\u003c!-- The function `evalString` takes an optional second argument to pass --\u003e\n\u003c!-- options. Read [here](#Usage) how to use those options. Instead of symbols and --\u003e\n\u003c!-- keywords it expects strings. Instead of kebab-case, use camelCase. --\u003e\n\n## Use as native shared library\n\nTo use SCI as a native shared library from e.g. C++, Rust, Python, read this [tutorial](doc/libsci.md).\n\n## `eval` in ClojureScript\n\n``` Clojure\n(require '[sci.core :as sci])\n(def ctx (sci/init {:classes {'js js/globalThis :allow :all}}))\n(set! *eval* #(sci/eval-form ctx %))\n(assoc {} :a (eval '(+ 1 2 3))) ;;=\u003e {:a 6}\n```\n\n## Async evaluation in ClojureScript\n\nSee [doc/async.md](doc/async.md).\n\n## Limitations\n\nCurrently SCI has limited support for `deftype` and does not support `definterface`.\n\n### This-as\n\nCurrently SCI does not support `this-as` in JS hosts. As a workaround you can program in this style:\n\n``` clojure\n(def obj\n  (let [;; construct the object:\n        this #js {:text \"foo\"}\n        ;; construct object functions:\n        setText (fn [text] (set! (.-text this) text))\n        getText (fn [] (.-text this))]\n    ;; attach object functions:\n    (set! (.-setText this) setText)\n    (set! (.-getText this) getText)\n    ;; return object:\n    this))\n\n(.setText obj \"hello\")\n(prn (.getText obj)) ;; \"hello\"\n```\n\n\u003c!-- A drop-in replacement that covers a subset of `this-as` usage might be implemented like this: --\u003e\n\n\u003c!-- ``` Clojure --\u003e\n\u003c!-- (defmacro ^:private new-var [] --\u003e\n\u003c!--   `(def ~(gensym))) --\u003e\n\n\u003c!-- (defmacro this-as [binding \u0026 body] --\u003e\n\u003c!--   `(let [~binding (new-var) --\u003e\n\u003c!--          obj# (do ~@body)] --\u003e\n\u003c!--      (alter-var-root ~(list 'var binding) (constantly obj#)) --\u003e\n\u003c!--      ;; remove var from namespace --\u003e\n\u003c!--      (ns-unmap *ns* (:name (meta ~binding))) --\u003e\n\u003c!--      obj#)) --\u003e\n\n\u003c!-- (def obj --\u003e\n\u003c!--   (this-as this --\u003e\n\u003c!--     #js {:text \"\" --\u003e\n\u003c!--          :setText (fn [t] (set! (.-text this) t)) --\u003e\n\u003c!--          :getText (fn [] (.-text this))})) --\u003e\n\n\u003c!-- (.setText obj \"hello\") --\u003e\n\u003c!-- (prn (.getText obj)) ;; \"hello\" --\u003e\n\u003c!-- ``` --\u003e\n\n## Laziness\n\nForms evaluated by SCI can produce lazy sequences. In Clojure, dynamic vars and\nlaziness can be a tricky combination and the same goes for dynamic SCI vars.\n\nConsider the following example:\n\n``` clojure\n(let [sw     (java.io.StringWriter.)\n      result (sci/binding [sci/out sw] (sci/eval-string \"(map print (range 10))\"))]\n  (println \"Output:\" (str sw))\n  (println \"Result:\" result))\n```\n\nIf the returned lazy seq was realized within the `sci/binding` scope, the output\nwould be:\n\n```\nOutput: 0123456789\nResult: (nil nil nil nil nil nil nil nil nil nil)\n```\n\nBut because the result is only printed outside of `sci/binding` the result is:\n\n```\nExecution error (ClassCastException) at sci.impl.io/pr-on (io.cljc:44).\nclass sci.impl.vars.SciUnbound cannot be cast to class java.io.Writer (sci.impl.vars.SciUnbound is in unnamed module of loader clojure.lang.DynamicClassLoader @4c2af006; java.io.Writer is in module java.base of loader 'bootstrap')\n```\n\nThis happens because by the time the lazy-seq is realized, the binding scope for\n`sci/out` is no longer established, and as a result the lazy-seq can no longer\nbe realized (due to the delayed calls to `println`, a side-effecting call\ndependents on the value of `sci/out`, set by `sci/binding`.\n\nIf the result is intended to be serialized as a string, then one could simply\nserialize while the binding is still in place:\n\n``` clojure\n(let [sw (java.io.StringWriter.)]\n  (sci/binding [sci/out sw]\n    (let [result (sci/eval-string \"(map print (range 10))\")]\n      (println \"Result:\" result)\n      (println \"Output:\" (str sw)))))\n```\n\nNote that we moved `(println \"Result:\" result)` before `(println \"Output:\" (str\nsw))`, since the first call takes care of realization.\n\n## Sci.configs\n\nThe [sci.configs](https://github.com/babashka/sci.configs) project contains\nready to be used SCI configs for several popular libraries.\n\n## Test\n\nRequired: `lein`, the `clojure` CLI and GraalVM.\n\nTo successfully run the GraalVM tests, you will have to compile the binary first\nwith `script/compile`.\n\nTo run all tests:\n\n    script/test/all\n\nFor running individual tests, see the scripts in `script/test`.\n\n## Dev\n\n### Benchmarking\n\nUse `clojure -M:bench` to benchmark the various phases of sci on the JVM:\n\n``` clojure\n$ clojure -M:bench --complete --sexpr \"(let [x 1 y 2] (+ x y))\" --quick\nBENCHMARKING EXPRESSION: (let [x 1 y 2] (+ x y))\nPARSE:\n-\u003e (let [x 1 y 2] (+ x y))\nEvaluation count : 1206396 in 6 samples of 201066 calls.\n             Execution time mean : 528,740641 ns\n    Execution time std-deviation : 35,961381 ns\n   Execution time lower quantile : 495,686332 ns ( 2,5%)\n   Execution time upper quantile : 580,676252 ns (97,5%)\n                   Overhead used : 1,900699 ns\nANALYSIS:\nEvaluation count : 98340 in 6 samples of 16390 calls.\n             Execution time mean : 6,837491 µs\n    Execution time std-deviation : 454,527892 ns\n   Execution time lower quantile : 6,115323 µs ( 2,5%)\n   Execution time upper quantile : 7,307643 µs (97,5%)\n                   Overhead used : 1,900699 ns\nEVALUATION:\n-\u003e 3\nEvaluation count : 31674576 in 6 samples of 5279096 calls.\n             Execution time mean : 16,949801 ns\n    Execution time std-deviation : 0,182429 ns\n   Execution time lower quantile : 16,796615 ns ( 2,5%)\n   Execution time upper quantile : 17,161758 ns (97,5%)\n```\n\nUse `--parse`, `--evaluate` and/or `--analyze` to bench individual phases\n(`--complete` will bench all of them). Leaving out `--quick` will run\n`criterium/bench` instead of `criterium/quick-bench`.\n\n#### GraalVM native-image\n\nTo benchmark an expression within GraalVM `native-image`, run `script/compile` and then run:\n\n``` clojure\n$ time ./sci \"(loop [val 0 cnt 10000000] (if (pos? cnt) (recur (inc val) (dec cnt)) val))\"\n10000000\n./sci    0.65s  user 0.02s system 99% cpu 0.669 total\n```\n\n## Troubleshooting\n\n### Shadow-cljs + user.clj\n\nWhen you require `sci.core` in `user.clj` in a shadow-cljs project, you might see problems with `sci.core/copy-var`.\n\nThis can be worked around by loading SCI like this in `user.clj`, instead of loading it in the `ns` form:\n\n``` Clojure\n(try (requiring-resolve 'cljs.analyzer.api/ns-resolve) (catch Exception _ nil))\n(require '[sci.core :as sci])\n```\n\n## Thanks\n\n- [Sponsors](https://github.com/sponsors/borkdude)\n- [Contributors](https://github.com/babashka/SCI/graphs/contributors) and other users posting issues with bug reports and ideas\n\n## License\n\nCopyright © 2019-2022 Michiel Borkent\n\nDistributed under the Eclipse Public License 1.0. This project contains code\nfrom Clojure and ClojureScript which are also licensed under the EPL 1.0. See\nLICENSE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabashka%2Fsci","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbabashka%2Fsci","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabashka%2Fsci/lists"}