{"id":16659814,"url":"https://github.com/bsless/contextual","last_synced_at":"2025-03-21T16:32:34.114Z","repository":{"id":46766004,"uuid":"308050121","full_name":"bsless/contextual","owner":"bsless","description":"Fast Clojure interpreter and template engine","archived":false,"fork":false,"pushed_at":"2021-10-15T08:44:06.000Z","size":186,"stargazers_count":14,"open_issues_count":4,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-17T19:48:54.873Z","etag":null,"topics":["clojure","interpreter","performance","template-engine"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bsless.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-28T14:55:34.000Z","updated_at":"2023-10-20T14:09:24.000Z","dependencies_parsed_at":"2022-09-18T06:36:10.946Z","dependency_job_id":null,"html_url":"https://github.com/bsless/contextual","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsless%2Fcontextual","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsless%2Fcontextual/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsless%2Fcontextual/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bsless%2Fcontextual/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bsless","download_url":"https://codeload.github.com/bsless/contextual/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244829597,"owners_count":20517338,"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":["clojure","interpreter","performance","template-engine"],"created_at":"2024-10-12T10:26:41.885Z","updated_at":"2025-03-21T16:32:33.665Z","avatar_url":"https://github.com/bsless.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Clojars Project](https://img.shields.io/clojars/v/bsless/contextual.svg)](https://clojars.org/bsless/contextual)\n[![cljdoc badge](https://cljdoc.org/badge/bsless/contextual)](https://cljdoc.org/d/bsless/contextual/CURRENT)\n[![CircleCI](https://circleci.com/gh/bsless/contextual/tree/master.svg?style=shield)](https://circleci.com/gh/bsless/contextual/tree/master)\n\n# Contextual\n\nA two-phase Clojure interpreter. Write an expression once, run it many\ntimes with good performance.\n\nDeferred evaluation of Clojure expressions with late bindings of input.\n\n## Not A Function\n\nIf you read the description above and say, \"that's just a function\",\nyou're right. So why not a function? Few reasons:\n- Dynamic input: creating functions willy-nilly from out-of-process\n  inputs is a potential nightmare.\n- Special contexts: By evaluating an expression in a special context, we\n  can use it to represent data templates. Example can be rendering HTML,\n  or even a predetermined HTTP request.\n- Symbolic manipulation.\n- Limited Metaspace: Clojure functions are compiled to unique class\n  instances. Class metadata is stored in the Metaspace. This space can\n  theoretically run out over the life time of a long running\n  application.\n\n## Like An Expression\n\nThis library allows the user to store and treat expressions as data, and\nsafely evaluate them in different contexts. Moreover, these expressions\ncan be safely generated based on user input and run inside your\napplication with reasonable performance.\n\n## Two Phase Interpreter\n\n- Compile: An expression is compiled to a class hierarchy representing its tree structure\n- Invoke: Evaluate the expression with given context. Only method calls, zero interpretations.\n\n## Scope\n\nCurrently contextual is not a complete Clojure interpreter, but it\nworks, it's fast, and can be used.\n\nAdding support for all of Clojure is on the roadmap, but features which\ndegrade performance might not be added.\n\n## Usage\n\nUsing contextual involves two phases: compilation and execution.\n\nExecution is always performed via `(contextual.core/invoke compiled-expr context-map)`\n\nCompilation options:\n\n- lookup: map from symbol to value. Used for resolving symbols during\n  compilation. Can contain any value, from primitive to function, i.e.\n  `{'foo clojure.core/println 'bar (-\u003epath :x y)}` is a valid lookup\n  map.\n- symbols registry: This is a map of special symbols to be resolved to\n  constructors for objects which implement the protocols `IContext` or\n  `IStringBuild`, to be used as new units of syntax and execution. An\n  extension point for users.\n\nCurrently, the following compilations are available:\n\n### Expressions\n\n`(contextual.core/compile expr)`, where `expr` can contain any of the\nsupported symbols or resolvable symbols.\n\n### HTTP Requests\n\n`(contextual.http/compile request)` takes a template of containing they\nkeys `url path query-params body form method headers`, any of which\nbesides `url` is optional, and emits an invokable which would emit a map\nwith a corresponding structure after invoking all the expressions\ncontained in it.\n\nSpecial HTTP options:\n- `serialize-body`: when not false-y indicates the request body should\n  be serialized with the provided `body-serializer`.\n- `body-serializer`: any function which will serialize the emitted\n  request body.\n- `serialize-form`: when not false-y indicates the request form should\n  be serialized with the provided `form-serializer`.\n- `form-serializer`: any function which will serialize the emitted\n  request form.\n- `serialize-query-params`: when truth-y will append the query params to\n  the end of the URL instead of emitting them as a map. i.e. `{:a 1 :b 2}` -\u003e `?a=1\u0026b=2`.\n\n## Validation\n\nThe templating system can perform best-effort validation.\n\nUse `contextual.validate/validate-expression`, which will report the following validations in a map:\n\n- `unresolvable-symbols`: All the symbols which could not be resolved at\n  expression compile time.\n- `bad-function-calls`: All instances of expressions with a wrong number\n  of arguments, function calls which aren't callable, and unresolved\n  symbols. This overlaps slightly with `unresolvable-symbols`.\n\n## Design\n\n### Protocols\n\n#### `IContext`\n\n- `-invoke [this ctx]`: Invoke the given object with context `ctx`.\n  Defaults to identity for `Object` and `nil`.\n\n#### `IStringBuild`\n\n- `-invoke-with-builder [this ctx sb]`: Invoke the given object with\n  context `ctx` and `StringBuilder` `sb`.\n  \n### Records\n\nContextual uses records to describe behaviors. They behave like their\ncorresponding clojure.core names would, with any difference noted below:\n\n- `Map`: map container which will `-invoke` every key and value with context.\n- `OptionalMapWrapper`: like map, but will discard values with\n  `:optional` metadata if they are nil.\n- `If`: Makes branching possible. Will invoke the predicate, then either\n  branch based on the result.\n- `Fn`: function container. Will `-invoke` all of a function's arguments\n  with context, then apply the function.\n- `Path`: a generic getter for a path of keys in `ctx`. `(path :x :y)`\n  will evaluate to whatever value is in path `[:x :y]` in the context\n  map.\n- `Or`/`And`.\n- `Str`: Will invoke all its arguments and add their non-nil result to a\n  string builder. Nested `Str`s won't create intermediary Strings but\n  will use the same StringBuilder.\n- `Let`: Works like you'd expect let to work. Lexical environment is\n  implemented via attached metadata on the context and environment\n  chaining.\n  \n### Constructors\n\nThe defined records aren't meant to be used directly, but are wrapped in\nlower case constructor functions.\nAn underlying optimization will dispatch to a loop-unrolled record when possible.\n\n### Compilation\n\nGiven an expression such as\n\n```clojure\n(if (path :x :y)\n  (let [x (path :a :b)]\n    (+ x 2))\n  (str (path :y :z) \"blah\" (path :u :w)))\n```\n\nCompilation will produce a tree of records representing its structure after a post-walk.\n\n```clojure\n#contextual.core.If{:p #contextual.core.Path2{:k0 :x, :k1 :y}, :t #contextual.core.Let{:bindings #contextual.core.Bindings{:bindings [[x__22910 #contextual.core.Path2{:k0 :a, :k1 :b}]]}, :expr #contextual.core.Fn2{:f #function[clojure.core/+], :a0 #contextual.core.Lookup{:sym x__22910}, :a1 2}}, :e #contextual.core.Str3{:a0 #contextual.core.Path2{:k0 :y, :k1 :z}, :a1 \"blah\", :a2 #contextual.core.Path2{:k0 :u, :k1 :w}}}\n```\n\n#### Symbol resolution\n\nCurrently, symbols are resolved via:\n\n- symbols-registry\n- namespace resolution\n- lookup in a map argument\n\nOtherwise, a symbol will be interpreted as an environment lookup.\n\n## Performance vs. SCI\n\nContextual optimizes for evaluation and string generation, being a template engine.\n\nSCI's analysis is faster than Contextual which has several optimization passes.\n\nBesides let forms, Contextual's evaluation is faster.\n\n### Analysis\n\n| Expression                      | SCI                   | Contextual            |\n| (+ 1 2)                         | 964.928006158732 ns   | 2.079940179533958 µs  |\n| (let [x 1 y 2] (+ x y))         | 4.053300853147959 µs  | 10.428633451833264 µs |\n| (let [x 1] (let [y 2] (+ x y))) | 5.014739082063033 µs  | 15.419620810055868 µs |\n| (str 1)                         | 940.5541048261234 ns  | 2.462470343113364 µs  |\n| (str 1 2 3)                     | 978.0085949105783 ns  | 3.3992453241441143 µs |\n| (str 1 2 3 4 5 6 7 8 9 10)      | 1.1567249302535807 µs | 6.230790269538975 µs  |\n| (str 1 (str 2 (str 3)))         | 2.7034542882428667 µs | 6.367253313202483 µs  |\n\n### Evaluation of analyzed expression\n\n| Expression                      | SCI                   | Contextual            |\n| (+ 1 2)                         | 35.082971812986024 ns | 8.572977104636808 ns  |\n| (let [x 1 y 2] (+ x y))         | 296.1578519129252 ns  | 239.01144451640243 ns |\n| (let [x 1] (let [y 2] (+ x y))) | 448.73383802345813 ns | 503.64078619481376 ns |\n| (str 1)                         | 25.416337334204073 ns | 19.849291428915592 ns |\n| (str 1 2 3)                     | 117.64023555640503 ns | 45.32831079503547 ns  |\n| (str 1 2 3 4 5 6 7 8 9 10)      | 409.09662405378765 ns | 124.85953596525712 ns |\n| (str 1 (str 2 (str 3)))         | 187.72209701166938 ns | 50.720662555853266 ns |\n\n\n## Status\n\nExperimental, in development\n\n## TODO\n\n- [X] Unit tests\n- [ ] Tuple records\n- [X] Map* records\n- [X] Ensure strings work (as advertised)\n- [ ] Generalize `StringBuilder` case to `Appendable`\n- [ ] Check option of similarly implementing `OutputStream`. Use `Writer`?\n- [X] Bring HTTP request builder up to workable condition.\n- [ ] Handle different types of expressions in request better (vector, expr, etc.)\n- [X] Faster walk?\n- [X] More macros, (cond!)\n- [X] Improve / control over resolution mechanism\n- [X] Expose only safe functions by default (nothing is exposed by default)\n- [ ] add namespaces\n- [ ] Basic interop\n- [ ] fns\n- [ ] POC tagged template.\n- [ ] Replace Records with types\n- [ ] Have types' string representation be homoiconic.\n\n## License\n\nCopyright © 2020 Ben Sless\n\nThis program and the accompanying materials are made available under the\nterms of the Eclipse Public License 2.0 which is available at\nhttp://www.eclipse.org/legal/epl-2.0.\n\nThis Source Code may also be made available under the following Secondary\nLicenses when the conditions for such availability set forth in the Eclipse\nPublic License, v. 2.0 are satisfied: GNU General Public License as published by\nthe Free Software Foundation, either version 2 of the License, or (at your\noption) any later version, with the GNU Classpath Exception which is available\nat https://www.gnu.org/software/classpath/license.html.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbsless%2Fcontextual","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbsless%2Fcontextual","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbsless%2Fcontextual/lists"}