{"id":19212499,"url":"https://github.com/fasterthanlime/shin","last_synced_at":"2025-05-12T20:39:12.852Z","repository":{"id":23053387,"uuid":"26406601","full_name":"fasterthanlime/shin","owner":"fasterthanlime","description":":warning: (def shin (dissoc clojurescript :jvm :google_closure)) (deprecated)","archived":false,"fork":false,"pushed_at":"2015-01-12T17:14:26.000Z","size":2042,"stargazers_count":35,"open_issues_count":19,"forks_count":1,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-20T17:40:30.498Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fasterthanlime.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-11-09T19:35:29.000Z","updated_at":"2023-06-04T00:25:38.000Z","dependencies_parsed_at":"2022-09-21T19:12:20.332Z","dependency_job_id":null,"html_url":"https://github.com/fasterthanlime/shin","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/fasterthanlime%2Fshin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fasterthanlime%2Fshin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fasterthanlime%2Fshin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fasterthanlime%2Fshin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fasterthanlime","download_url":"https://codeload.github.com/fasterthanlime/shin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253817615,"owners_count":21969009,"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":[],"created_at":"2024-11-09T13:47:09.836Z","updated_at":"2025-05-12T20:39:12.830Z","avatar_url":"https://github.com/fasterthanlime.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n[![Build Status](https://travis-ci.org/memoways/shin.svg?branch=master)](https://travis-ci.org/memoways/shin)\n\n# shin\n\nShin is a very immature Clojure subset that compiles to JavaScript thanks\nto Ruby and V8.\n\nIt's heavily inspired by:\n\n  - [ClojureScript][cljs]\n  - The [ki language][ki]\n\n## How to play with it\n\nBefore we get into details, here's how you can get shin up and running to\nplay around with it.\n\n  - Make sure you have a recent Ruby (2.1 is recommended) \u0026 rubygems\n  - Clone `shin` in a directory\n  - Make another directory alongside it, say `shin-playground`\n  - Create `shin-playground/Gemfile` and populate it with:\n\n```ruby\nsource \"https://rubygems.org\"\n\ngem \"shin\", :path =\u003e \"../shin\"\n```\n\n  - Run `bundle install` in `shin-playground`\n  - You can now run `bundle exec shin` to run shin.\n\nUltimately, there'll be releases of shin and installing will be as easy\nas `gem install shin` but since, so far, things are moving quite quickly,\nit's just easier to have people playing with master.\n\n### Basic command line usage\n\nEval one-liners:\n\n```\n$ bundle exec shin -e \"(println (reduce + (map #(* % 2) (take 10 (range)))))\"\n90\n```\n\n(Note: the return value is ignored, so use `print` / `println` to get your\nresults back.)\n\nCompile a project with libraries\n\n```\n$ bundle exec shin -o public/js -L app/js -I app -I vendor/reagent-shin/src app/my-cool-app/app.cljs\n```\n\nWill output all needed .js files in `public/js`.\n\n`-I` is the search path for .cljs files, `-L` is the search path for .js files.\nTested in the browser with Require.js.\n\n## Rationale\n\nClojureScript is great, but, for the time being, the tooling is [a bit\nheavy-handed][shameless-plug], slow at times, and definitely tied to the JVM.\n\nWhy write Shin in Ruby? Ruby, while problematic in many regards and anything\nbut \"small\", is a language most of the Memoways team is comfortable with. It\nhas a rich library ecosystem that allows one to tackle most problems quickly.\n\nPlus, MRI start-up times are much smaller than usual JVM start-up times.\n(At this time, no attempt has been made to run Shin under Rubinius, JRuby,\nor any other Ruby implementation.)\n\n## Design philosophy\n\nShin generates relatively naive JavaScript code. At the time of this writing,\nno attempt is made a optimizing anything. Rather, the compiler is designed to\ngenerate correct and readable code, quickly.\n\n## Bird's eye view\n\n  - `compiler.rb` - drives the whole thing\n  - `parser.rb` - text to `Shin::AST` representation\n  - `ns_parser.rb` - recognizes `ns` form, populates `requires` and\n  generate anonynmous ns name if lack of ns form.\n  - `macro_expander.rb` - expands macros\n  - `translator.rb` - translates `Shin::AST` to `Shin::JST`, which is\n  [Mozilla Parser API][moz-parser-api] in disguise.\n  - `generator.rb` - generates actual JavaScript from the JST, via\n  [escodegen][].\n\n## Parser\n\nTried out [treetop][] but not a good tool for proper error reporting, as\nS-exps are basically one big tree, and PEG grammars would just see that\nas one big invalid input - we want to acknowledge that, up to that point\ndeep into the tree, everything was fine, and then something went wrong.\n\nActual parser inspired by [sxp-ruby][], some I/O routines lifted from there\n(see UNLICENSE - it's public domain).\n\n### S-exp patterns\n\nUse case: sniff patterns like `(defn name [args] body)` in the code,\nmostly used by translator.\n\nTypes: `:sym`, `:kw`, `:num`, `:str`, `:map`, `:list`, `:vec`, `:set`,\nverbatim identifiers, verbatim strings, verbatim numbers, sequences\nwithout content (matches any) or with inner patterns.\n\nRegexp-like operators:\n\n  - `?` - 0 or 1\n  - `*` - 0 or more (greedy)\n  - `+` - 1 or more (greedy)\n\nWhen `*` or `+` used, matcher block will receive an Array of nodes.\nWhen `?` used, may receive null if doesn't match.\n\nFor examples, see `spec/infra/matcher_spec.rb`.\n\n## Translator\n\nBasically walks the AST and produces a JST. For now, some forms that could\nbe implemented as macros are recognized by the translator, for example `defn`.\n\nFor example, literals are transformed to calls to `vector`, `list`, `hash-map`,\n`symbol`, `keyword`, etc. Function definitions are transformed into JavaScript\nfunctions, auto-returning the last value.\n\n`if`, `let`, and `do` forms emit relatively naive but efficient code where possible.\n\nFor `if`, when the value is thrown away (`statement` mode), JavaScript's\n`if/else` will be used. Otherwise, the ternary operator (`cond ? iftrue : iffalse`)\nwill be used.\n\n`let` establishes a new scope, and renames bound symbols inside\nof it, uses of which are then resolved to aliases, much like [Traceur][] does\nwith ES6's `let`.\n\n`do` can be compiled either to a simple block, or to a closure, if the result\nis used as an expression (it doesn't make a closure if it's just in return position,\nin that case, return propagation is used).\n\n`def`s are local variable declarations + stored into the module's `exports`\nobject (see `Modules` section).\n\nUsage of the `@` operator are translated to `deref` calls. Usage of `~` and\n`~@` are translated to `--unquote` calls (used internally for macro expansion).\n\nSome checks are only done at translation time - for example, the number of keys\nin a map literal, or in a let bindings vector.\n\nAs a rule, the translator generates pretty errors, like the following:\n\n```\n$ be shin -o public/js -L src/js -I src src/my/app.cljs\n\nInvalid let form: odd number of binding forms at src/my/app.cljs:15:6 (RuntimeError)\n\n(let [initial-tree (render) a]\n      ~~~~~~~~~~~~~~~~~~~~~~~~\n```\n\nThe output of the translator is what the compiler calls `JST`, which is just\nreally [mozilla parser API][moz-parser-api] stored in Ruby data structures.\n\n## Generator\n\nUses [escodegen][] for convenience, to generate JavaScript from the JST.\n\nWhy use escodegen?\n\n  - Proven codebase\n  - Standard JavaScript AST format ([Mozilla Parser API][moz-parser-api])\n  - Plays well with JavaScript source-maps (even tho not using that yet)\n\n(So, yes, it calls into V8 to generate JS code.)\n\nSee `generator.rb`, it's pretty trivial. `jst.rb` is a thin layer of\nRuby structures above the Moz Parser API.\n\nThe only thing we can't represent is RegExp literals, see RegExp section.\n\nNot much to say about the generator - most of the work is done in the\ntranslator anyway.\n\nCould replace escodegen with a pure Ruby solution for performance at some point,\nmaybe use [sourcemap.rb][] or a more up-to-date fork thereof?\n\n## Modules\n\n### AMD\n\ncljs chose Google Closure, shin consumes \u0026 generates AMD modules.\n\nFor those, who don't know, AMD modules look like:\n\n```javascript\n(function (root, factory) {\n  if (typeof define === 'function' \u0026\u0026 define.amd) {\n    define(['exports', 'foo', 'bar'], factory);\n  } else {\n    throw \"No AMD loader in sight.\";\n  }\n})(root, function (exports, foo, bar) {\n  // Execution reaches here whenever `foo` and `bar` are\n  // loaded, or we have circular dependencies. Be careful.\n  exports.dostuff = function() {\n    return new foo.SomeType(bar.somefunction());\n  });\n\n  // Anything stuffed in `exports` will be available\n  // to others. In fact, that's how foo and bar exposes\n  // stuff to us in the first place!\n});\n\n```\n\nTo run specs \u0026 expand macros, shin compiler has an AMD loader built-in,\nhalf-ruby half-JS. See `js_context.rb`.\n\n### Namespaces\n\nEvery `.clj(s)` file read by Shin must start with a single namespace\ndefinition, here's an example:\n\n```clj\n(ns org.example.somemodule\n  (:require [some.lib :as sl :refer :all])\n            [another.lib :refer [foo bar baz]]\n            [js/react :as R])\n```\n\nUse the `js/` prefix to specify that you're requiring a JS module, not\na Shin module. The compiler will look into libpath and attempt to copy\nit to the output directory, and warn if it can't find it. It'll also be\nlisted as a dependency in the AMD module definition.\n\n### Supported loaders\n\nShin's output should be usable with [RequireJS][] in a browser. But\nif you want to try and see if it works with [Almond][], that's cool too!\n\n### Directory structure\n\nAs for directory structure, any `package` can either be a folder\nor a dot in the pathname, so if you require `org.example.somelib.a`,\nit will look for:\n\n  - `$SOURCEPATH/org/example/somelib/a.cljs`\n  - `$SOURCEPATH/org/example/somelib.a.cljs`\n  - `$SOURCEPATH/org/example.somelib.a.cljs`\n  - `$SOURCEPATH/org.example.somelib.a.cljs`\n\nI'm 83% sure that's not what cljs does, but it seemed relatively sane -\ngo full folder if you want to, or keep your structure as flat as you want,\ndoesn't matter to shin.\n\n### Non-AMD (Node.js)\n\nThere's half-baked support for generating NodeJS-style `require` directives\nbut it hasn't been tested whatsoever.\n\n### Builtins\n\nShin ships with some pure JS files:\n\n- `hamt.js` is [hamt+][], for data structures\n- `escodegen.js` is [escodegen][], only there for compiler use\n\nAnd of course, `cljs/core.cljs` and `cljs/core.clj` (for macros),\nwhich are required (with :refer :all) into all modules by default.\n\n(`:refer-clojure` is not implemented yet, so no exclusion is possible.\nSee [Issue #20](https://github.com/memoways/shin/issues/20))\n\n### core lib Completion\n\nAt this point, a sizable portion of `cljs.core` has been ported over, but\nan even more sizable portion remains untouched. Hopefully the balance will\ntip eventually, but with 611 vars in `clojure.core` at the time of this\nwriting, my money's on Rich.\n\nThe full `clojure.string` package has been adapted from ClojureScript and\nis covered by specs.\n\n## Specs\n\nRSpec 3.x, ran [on travis-ci][travis].\n\nTwo types of specs:\n\n### Infrastructure specs\n\nFor now, mostly testing the S-exp pattern matching system.\n\nCustom matcher, `ast_match` (see `spec/matchers.rb`), basic structure\nis `expect(sexp_source).to ast_match(sexp_pattern)`.\n\n### Language specs\n\nCustom matcher `have_output` (see `spec/matchers.rb`), basic structure\nis `expect(code).to have_output(output)`. Shin code calls out to `print`,\nall calls joined by a space character.\n\nSimple:\n\n```ruby\nRSpec.describe \"Language\", \"if\" do\n  it \"doesn't evaluate if-false, when false\" do\n    expect(%Q{\n      (if true () (print \"Don't print me!\"))\n    }).to have_output(\"\")\n  end\nend\n```\n\nWith macros:\n\n```ruby\nRSpec.describe \"Language\", \"macros\" do\n  it \"separates compile-time \u0026 run-time correctly\" do\n    expect(\n      :source =\u003e %Q{ (let [a (foobar)] (print (typeof a))) }\n      :macros =\u003e %Q{ (defmacro foobar [] `(print \"That'll be at runtime, thanks\")) }\n    ).to have_output(\"function\")\n  end\nend\n```\n\nYes, it'd be cleaner if one didn't have to write `print` in every spec, but you'd\nbe surprised what you can't test with such rudimentary infrastructure.\n\n## Mangling\n\nSince Clojure is very liberal in what identifiers are, and JavaScript isn't,\nShin mangles identifiers.\n\nFor example, `contains?` is mangled to `contains$q`. `$` itself is escaped as\n`$$`. As most of Shin, it's so naive it's cute, but hey, it works really well.\n\nSee `mangler.rb` to see the most current translation table. If you ever modify\nthat, keep `shin.js` in sync.\n\n## Macros\n\nBasic idea: macros are executable code, so let's compile \u0026 execute them.\nThat's `macro_expander.rb`'s job.\n\n*The following description is out of date since the fast-macros branch*\n\nWhen macro expansion is required:\n\n  - Compile macro def to JS, as if it was a function\n  - Compile dummy module that does `(yield (pr-str (macro-invocation)))`\n  - `pr-str` will serialize our code to textual S-expr representation\n  - `yield` will give it back to the Ruby side (compiler)\n  - We then parse it back so we have a `Shin::AST` repr of the result\n  - Then we replace the invocation by its expansion\n  - Tadaa.\n\nThat means we actually invoke V8 during compilation, if macros are involved.\nAlso, the order in which stuff is done is still kind of shady, especially\nconsidering `cljs.core` is required (with `:refer :all`) everywhere by\ndefault. If macros break don't hit me!\n\n`gensym` manual calling is supported, however the compiler believes in big\nnumbers and will not attempt to check that they, in fact, do not collide with\neach other (so far).\n\nAutomatic `gensym` is in, which means you can write stuff like that:\n\n```\n(defmacro nonsense [a b c]\n  `(let [d# (str ~a ~b)]\n     (print d# ~c d#)))\n```\n\nAnd it'll be transformed (at parse time) to something like that:\n\n```\n(defmacro nonsense [a b c]\n  (let [d2121 (gensym \"d\")]\n    `(let [~d2121 (str ~a ~b)]\n      (print ~d2121 ~c ~d2121))))\n```\n\n## Destructuring\n\nFull Clojure destructuring is supported, with vectors and maps, in let forms,\nfunction definitions and macro definitions, nested as deep as you want.\n\nIt's one of the reason Clojure is so cool, read about it:\n\n  - [On clojure.org](http://clojure.org/special_forms#Special%20Forms--Binding%20Forms%20(Destructuring))\n  - [On clojuredocs.org](http://clojuredocs.org/concepts/destructuring)\n  - [On Jay Field's blog](http://blog.jayfields.com/2010/07/clojure-destructuring.html)\n\n## Data structures\n\nUsing [hamt+][] by Matt Bierner. Doesn't use [mori][] because, c'mon,\n[where's the fun][structs-in-js] in that?\n\nEverything is immutable, persistent. Manipulation is slower than\nnative JS structures, but comparing should be faster. Watch\n[what Szymon says](http://vimeo.com/86694423) if you don't know\nabout Clojure-style persistent data structures.\n\n### PersistentArrayMap\n\nStraight up [hamt+][] tries, wrapped in a callable object (see `shin.js` for\nthe trick) so it behaves like a Clojure map.\n\nTakes advantages of [hamt+][]'s `mutate` so that huge literals are faster\nto construct than `assoc`'ing one by one. Could use the same trick to make\na fast JSON loader, for instance.\n\n### Sets\n\nNot even a real type, right now just a `PersistentArrayMap` with values set\nto `true`. Needs a whole lot of love.\n\n### PersistentVector\n\nStraight up naive, basically a PersistentArrayMap with integer keys, but\n[hamt+][] doesn't guarantee key ordering and probably does sub-optimal\nhashing, so we should come up with something better, probably a fork\nof [hamt+][] based on [ancient-oak][]'s work on arrays/vectors.\n\n### List\n\nSimilar to ClojureScript\n\n### Cons\n\nSimilar to ClojureScript\n\n### LazySeq\n\nNone yet, see [Issue #2](https://github.com/memoways/shin/issues/2).\n\n### Keyword, Symbol\n\nCallable (to allow `(:mykey map)` or `('mysym map)`, usable in calls\nto `name`, e.g. `(= \"foobar\" (name :foobar))` is true, same goes for\nsymbols.\n\n## Regexps\n\nMostly ClojureScript-equivalent with `re-find`, `re-matches` and `re-matcher`.\n\nWeird API but whatevs, clj gonna be clj.\n\nRegExp literal, `#\"[A-Z]IAMA a RegExp AMA\"`, translates to RegExp constructor\ncall rather than JS RegExp because that's the only part of the [Mozilla Parser\nAPI][moz-parser-api] that does *not* serialize to JSON. :hankey:\n\n## Atoms\n\n`atom`, `add-watch`, `remove-watch` and the deref operator `@` work pretty much\nas advertised. Implementation is deceptively simple though, don't way you weren't\nwarned.\n\n## Special forms\n\nWe've got:\n\n  - if\n  - let\n  - fn\n  - do\n  - quote\n  - def, defn, defmacro\n  - closures, e.g. `#()`\n  - loop/recur\n\nThere is no try (yet).\n\n## JS interop\n\nPrimitives from ClojureScript, e.g.\n\n```clj\n(let [o (Thing. \"foo\")] ; 'o' is now bound to a new Thing instance\n  (.setName o \"fido\") ; create a new 'MyObject' with arguments a, b, c\n  (prn \"Dog name: \" (.-name o)) ; access field 'name' from fido\n  )\n```\n\nAlso: `aget`, `aset`, `clj-\u003ejs`, and `js-\u003eclj`.\n\nLifts JS literals from [ki][], e.g.\n\n```clj\n(let [o {$ \"a\" 1 \"b\" 2} ; o is {a: 1, b: 2} in JS\n      a [$ 1 2 3]])     ; a is [1, 2, 3] in JS\n```\n\n## Types / OO\n\nInternal data structures are JS OO, but `deftype`, `defprotocol`, `satisfies?`,\n`reify`, etc. aren't implemented yet.\n\n@webbedspace has an [interesting idea for records](https://twitter.com/webbedspace/status/535423142286479360),\nthanks to @jcoglan for yelling about stuff.\n\n## Interfaces\n\n### CLI\n\nRun `shin -h` and it'll tell you all about it. Subject to changes.\n\n### API\n\n*Very* subject to changes, haven't found the right mix of convenient interface,\nflexible pipeline, concise code yet. Time will tell.\n\nBut basically, use `compiler.rb` or roll your own pipeline by running\nParser, NsParser, MacroExpander, Translator, Generator, and outputting yourself.\n\n## Thanks\n\nThanks to [Luca Antiga](https://twitter.com/lantiga) for the inspiration and\nfor ki, and to Nicolas Goy, Meine Goy, Fabrice Truillot de Chambrier, Ulrich\nFischer, Dimiter Petrov, Jens Nockert, Romain Ruetschi, Arnaud Bénard, for\nall the support.\n\nAnd of course, to the whole [ClojureScript][cljs] team for leading the way\nwith this whole \"Clojure to JavaScript\" thing!\n\n## License\n\nShin is released under the MIT license. See `LICENSE.txt` for the complete text.\n\nHamt+ is also MIT-licensed, see its own repo: [hamt+][].\n\nSignificant parts of standard library is adapted / copied from\n[ClojureScript][cljs], which is distributed under the Eclipse Public License\n1.0. Here's it's copyright license, just in case:\n\n```\nCopyright (c) Rich Hickey. All rights reserved. The use and\ndistribution terms for this software are covered by the Eclipse\nPublic License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)\nwhich can be found in the file epl-v10.html at the root of this\ndistribution. By using this software in any fashion, you are\nagreeing to be bound by the terms of this license. You must\nnot remove this notice, or any other, from this software.\n```\n\n[cljs]: https://github.com/clojure/clojurescript\n[ki]: http://ki-lang.org/\n[shameless-plug]: http://fasterthanlime.com/blog/2014/sexps-in-your-browser/\n\n[treetop]: https://github.com/nathansobo/treetop\n[sxp-ruby]: https://github.com/bendiken/sxp-ruby/\n\n[escodegen]: https://github.com/Constellation/escodegen\n[moz-parser-api]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API\n[sourcemap.rb]: https://github.com/maccman/sourcemap\n\n[travis]: https://travis-ci.org/memoways/shin\n\n[hamt+]: https://github.com/mattbierner/hamt_plus\n[ancient-oak]: https://github.com/brainshave/ancient-oak/\n[mori]: https://github.com/swannodette/mori\n[structs-in-js]: https://gist.github.com/fasterthanlime/d682abf83bdf624f0ef8\n\n[RequireJS]: http://requirejs.org/\n[Almond]: https://github.com/jrburke/almond\n[Traceur]: https://github.com/google/traceur-compiler\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffasterthanlime%2Fshin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffasterthanlime%2Fshin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffasterthanlime%2Fshin/lists"}