{"id":21020943,"url":"https://github.com/awto/mixfix-clj","last_synced_at":"2025-05-15T08:31:55.913Z","repository":{"id":57716761,"uuid":"41327734","full_name":"awto/mixfix-clj","owner":"awto","description":"mixfix syntax for clojure","archived":false,"fork":false,"pushed_at":"2015-10-02T16:47:42.000Z","size":190,"stargazers_count":50,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2023-10-25T17:05:50.236Z","etag":null,"topics":["clojure","edsl","macros","mixfix-syntax","parser-combinators"],"latest_commit_sha":null,"homepage":null,"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/awto.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":"2015-08-24T21:22:16.000Z","updated_at":"2022-09-02T23:08:09.000Z","dependencies_parsed_at":"2022-08-24T01:50:46.717Z","dependency_job_id":null,"html_url":"https://github.com/awto/mixfix-clj","commit_stats":null,"previous_names":[],"tags_count":2,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awto%2Fmixfix-clj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awto%2Fmixfix-clj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awto%2Fmixfix-clj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/awto%2Fmixfix-clj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/awto","download_url":"https://codeload.github.com/awto/mixfix-clj/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254304639,"owners_count":22048445,"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","edsl","macros","mixfix-syntax","parser-combinators"],"created_at":"2024-11-19T10:44:05.742Z","updated_at":"2025-05-15T08:31:55.546Z","avatar_url":"https://github.com/awto.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mixfix-clj\n\nProvides mixfix syntax for Clojure language.\n\nIt simply allows writing Clojure expressions like this:\n\n```clojure\n\n(defn myfun [x y] \n   (if x \u003c 2 then x + y - 1 else (x + y) * 2))\n\n```\n\nYou can also easily define nice syntax of your next EDSL.\n\nFor example some SQL-like\n\n```clojure\n(exec (select * from table1, table2 where col1 \u003c col2 group by col1, col2)) \n``` \n\nThe `exec` there is user-defined macros. It uses this library \n`mixfix.clj.core\\parse` function to convert concrete SQL-like\nsyntax into abstract syntax tree. This tree is plain Clojure form, and it easy \nto analyze or execute or convert into some DBMS query syntax using standard \nClojure means, like `clojure.walk`.\n\nThis is just simple macros, no extra build steps are required.\n\n## Usage\n\nInstall it using Clojars:\n\n[![Clojars Project](http://clojars.org/mixfix-clj/latest-version.svg)](http://clojars.org/mixfix-clj)\n\nImport the library:\n\n```clojure\n(ns sample.mixfix\n  (:require [mixfix.clj :as m]))\n```\n\nor for ClojureScript\n\n```clojure\n(ns sample.mixfix\n  (:require-macros [mixfix.clj :as m]))\n```\n\nNow define some operators:\n\n```clojure\n\n(m/op 400 + [[] + [+]])\n(m/op 400 - [[] - [+]])\n(m/op 500 * [[] * [+]])\n(m/op 500 / [[] / [+]])\n(m/op 200 or [[] or [+]]) \n(m/op 300 and [[] and [+]]) \n(m/op 400 = [[] is []])\n(m/op 100 if [if [+] then []])\n(m/op 110 if [if [+] then [] else []])\n```\n\nAnd use them:\n\n```clojure\n\n\n(m/% 2 + 2) ; ==\u003e 4\n(m/% 2 - 2 - 2)  ; ==\u003e -2\n(m/% 2 - (2 - 2)); ==\u003e 2\n(m/% let [x 2 y 2] (x + y - 2)); ==\u003e 2\n(m/% if 2 is 2 then if 3 is 4 then 5 else 6); ==\u003e 6\n\n;; it also composes with plain clojure application forms: \n\n(m/% 2 + (- 2 2)) ; ==\u003e 2\n\n```\n\nThe arguments for `op` are:\n\n  1. optional language name\n  2. precedence level, the bigger the number the tightly the operator binds\n  3. head symbol for clojure application list the operator will be converted to\n  4. syntax picture\n\nSyntax picture is a vector of symbols interleaved with another vectors \nspecifying syntax holes. The hole definition vector may contain various options. \nIn the current version they may be either: \n\n  * `empty` - means same precedence level as its operator\n  * `number` - specifies any precedence explicitly\n  * `+` - precedence is precendence of the operator plus one\n  * `assoc` - will unwrap sub-form if it has same head symbol as the operator\n  * `id \u003cvalue\u003e`  - for assoc operators will treat as identity for the \n    operation, by default `nil`.\n\nSo this is it. Mixfix operators are converted into plain clojure application \nform using ` %` macros. It walks through all sub-forms and parses their content \ntoo. There is also shallow version `%1` which parses only a single level.\n\nThere is also `mixfix.clj/defn` macros which simply redirects to \n`clojure.core/defn` but wraps arguments with operators parsing macros.\n\nPlain clojure application may be also converted back into mixfix syntax.\n\n```clojure\n\n(m/to-mixfix (- (+ (- (+ 1 2) 3) 4) 5))); ==\u003e (1 + 2 - 3 + 4 - 5)\n\n``` \n\nIt matches syntax definitions by arity, so if there are ambiguous symbol name\nplus arity it may fail to do this property.\n\n## Associative operators\n\nClojure often permits many arguments in an expression for typically binary \noperators, such as `clojure.core/+` etc. The library can handle such operators \ntoo. For this in the vector of syntax hole definition add  `assoc` option and \noptionally identity symbol for that operation. For example for addition:\n\n```clojure\n\n(op 400 + [[assoc id 0] + [+]])\n\n; now + will be parsed into single `+` form\n\n(macroexpand '(r/% 1 + 2 + 3)) ; ==\u003e (+ 1 2 3)\n(macroexpand '(r/% 0 + 1)) ; ==\u003e (+ 1)\n\n```\n\nThis isn't useful much for arithmetic operators unless generated code must\nbe readable. But it is useful for example for `clojure.core/list`. \n\n## Interleaving with clojure applications\nThere are two ways to use plain clojure application forms inside mixfix syntax. \nBy default there is an operator for space or comma (and it is the only \npredefined operator in this version of the library).\n\n```clojure\n\n(op 1000 form [[assoc] [+]])\n\n```\n\nThe library provides an auxiliary macros `mixfix.clj.core/form` it \nsimply splices its arguments in a list without doing with them anything. So as \na result it will be plain clojure application. For example\n\n```clojure\n\n(m/op 200 if [if [+] then []])\n\n(clojure.walk/macroexpand-all '(% if = 2 2 then :t)) \n; ==\u003e (if (= 2 2) (do :t))\n\n```\n\nThis option may be not convenient to detect syntax error sometimes, for example\nif we define \"==\" operator but accidently use \"=\" instead. \n\n```clojure\n(clojure.walk/macroexpand-all '(% if 2 = 2 then :t)) \n; ==\u003e (if (2 = 2) (do :t))\n```\n\nAnd clojure will complain about \"2\" isn't function, and this may be confusing. \nThis is an issue only for operators clashing with predefined function or macros \nnames in scope. The library will only accept them if it can `clojure.core/resolve` \nall the items of the list. It is also possible to disable such behavior by \nremoving such operator with: \n\n```clojure\n(m/remove-op form)\n```\n\nIn this case clojure plain application can still be parsed but it must be in \nparens. This is a kind of parenthesis symbols overloading. They may be used for \ngrouping mixfix sub-expressions and for specify clojure applications.\n\n```clojure\n(clojure.walk/macroexpand-all '(% if (= 2 2) then :t)) \n; ==\u003e (if (2 = 2) (do :t))\n```\n\nAfter the library detected parser error within parens it will try to interpret \nthem as a plain clojure list. Library will conclude the form is ok if all \nsymbols there can be resolved. This behavior may be also turned off using \n`mixfix.clj.core/*clojure-apps*` dynamic variable if your EDSL \ndoesn't need it. After only mixfix predefined operators can be present in \nparsed expression.\n\nAnother thing may be useful for custom EDSL, is `mixfix.clj.core/*locals*` \nvariable, which is a set of symbols bound to some local variable in a form \ncurrently parsed. By default it is inited from \u0026env parameter, but for custom \nEDSL, if it has some custom bound names they must be added to the set.\n\n## Syntax scopes\n\nIf some operators belong only to some EDSL (passed as parameters to some macros)\nthey may be assigned to some named scope. This scope can be used in parse \nfunction to convert it to plain clojure form for further handling by EDSL \nimplementation. \n\nSuch scope is defined using `mixfix.clj.core/declare-lang` macros. The\nfirst parameter is a name of the scope. The second optional parameter is another \nscope where initial operators' definitions are to be copied from. It creates \na variable with the same name which is used for referencing the scope. It may be\npassed as the optional first argument in `op` directives. And it may be passed \nto `mixfix.clj.core/parse` function via dynamic variable \n`mixfix.clj.core/*lang*` form the macro receiving EDSL expressions as \nparameters. Variable `mixfix.clj.core/global` is used as default\nscope. There is also corresponding macros `%*` with additional parameter for the\nscope specification.\n\nFor example defining SQL-like syntax:\n\n```clojure\n(m/declare-lang sql)\n(m/op sql 100 select [select [+] from [+] where [+] group by [+]]) \n(m/op sql 100 select [select [+] from [+] where [+]]) \n(m/op sql 100 select [select [+] from [+]]) \n(m/op sql 200 list [[assoc] [+]])\n(m/op sql 150 = [[+] = [+]])\n(m/op sql 150 \u003c [[+] \u003c [+]])\n; ......\n\n```\n\nIt looks a bit verbose, especially if the language size will grow. But, since\n`op` there is only a macros (not clojure syntax part), it may be easily \ngenerated. Or some next version will provide picture syntax for this.\n\nThese syntax further is parsed into AST with `mixfix.clj.core/parse`\nfunction with `mixfix.clj.core/*lang*` variable bound to `sql` \nvariable.\n\n## Limitations\n\nIf some library implements its own EDSL syntax parser it will not compose well \nwith this library. An example is `clojure.test/is`. It may take expected \nexception thrown specification with `thrown?` keyword. It is not a macros and \nit is not a function. It is just a part of another language `clojure.test/is` \ncan understand. On the other hand mixfix-clj doesn't know anything about this \nkeyword. So it will report parser error. It could ignore this and leave the \nform as is but it would significantly reduce diagnostic capabilities. There is \na macros for registering such kind of keywords (namely \n`mixfix.clj.core/reg-sym`). But even registered it won't work anyway. \nNot the problem is ordering of macros expansion. But if the library also uses \nmixfix-clj for syntax parsing it should work without problems.\n\nAt the moment there is no namespaces support for operator's part. They are \nsimply compared by `clojure.core/name`. But their support is planned for some\nnext version. This will be another level of operations scoping.\n\n## TODO:\n\n * bindings (now only clojure subforms such as let, or fn will be considered)\n * better diagnostics\n\n## License\n\nCopyright © 2015 Vitaliy Akimov\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fawto%2Fmixfix-clj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fawto%2Fmixfix-clj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fawto%2Fmixfix-clj/lists"}