{"id":15293302,"url":"https://github.com/dzangfan/resyma","last_synced_at":"2025-03-24T13:44:29.926Z","repository":{"id":117503486,"uuid":"599584012","full_name":"dzangfan/resyma","owner":"dzangfan","description":"Creating DSL by regular expressions","archived":false,"fork":false,"pushed_at":"2023-03-07T12:20:00.000Z","size":84,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-04-29T00:20:44.357Z","etag":null,"topics":["dsl","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dzangfan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-02-09T13:01:22.000Z","updated_at":"2023-02-09T13:13:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"daf5a3ea-0c18-4168-8923-8afd30a4d683","html_url":"https://github.com/dzangfan/resyma","commit_stats":{"total_commits":49,"total_committers":1,"mean_commits":49.0,"dds":0.0,"last_synced_commit":"a4ceec36b0872d4c80390467fb9737724f5a0fad"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzangfan%2Fresyma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzangfan%2Fresyma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzangfan%2Fresyma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzangfan%2Fresyma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dzangfan","download_url":"https://codeload.github.com/dzangfan/resyma/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245284357,"owners_count":20590306,"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":["dsl","ruby"],"created_at":"2024-09-30T16:46:08.847Z","updated_at":"2025-03-24T13:44:29.883Z","avatar_url":"https://github.com/dzangfan.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A DSL Engine\n\n## Introduction\n\n```ruby\nrequire \"resyma\"\nrequire \"date\"\n\n#\n# Define a new language by creating a subclass of Resyma::Language and\n# specifying the syntax in method 'syntax'\n#\nclass LangDate \u003c Resyma::Language\n  # syntax of 'syntax': regex \u003e\u003e action\n  def syntax\n    # e.g. today\n    id(\"today\") \u003e\u003e Date.today\n\n    # e.g. 2023/1/1\n    (int; id(\"/\"); int; id(\"/\"); int) \u003e\u003e begin\n      year = nodes[0].to_ruby\n      month = nodes[2].to_ruby\n      day = nodes[4].to_ruby\n      Date.new(year, month, day)\n    end\n\n    # e.g. +1.year\n    (numop; numbase; \".\"; [id(\"day\"), id(\"month\"), id(\"year\")]) \u003e\u003e begin\n      op, num, _, unit = nodes\n      sig = op.to_literal == \"+\" ? 1 : -1\n      val = num.to_literal.to_i * sig\n      case unit.to_literal\n      when \"day\" then Date.today.next_day(val)\n      when \"month\" then Date.today.next_month(val)\n      when \"year\" then Date.today.next_year(val)\n      end\n    end\n\n    # Recursively interpret\n    id(\"yesterday\") \u003e\u003e LangDate.load { -1.day }\n    id(\"tomorrow\") \u003e\u003e LangDate.load { +1.day }\n  end\nend\n\ndef date(\u0026block)\n  LangDate.load(\u0026block) # Interpret a block as DSL\nend\n\ndate { today }    #=\u003e #\u003cDate: 2023-02-09 (...)\u003e\ndate { tomorrow } #=\u003e #\u003cDate: 2023-02-10 (...)\u003e\ndate { 2024/2/9 } #=\u003e #\u003cDate: 2024-02-09 (...)\u003e\ndate { +7.day }   #=\u003e #\u003cDate: 2023-02-16 (...)\u003e\ndate { -3.month } #=\u003e #\u003cDate: 2022-11-09 (...)\u003e\n```\n\n`Resyma` is a draft of a DSL engine. We prevent blocks containing DSL from evaluating, apply our matching algorithm to the parse tree, and pass matched nodes to libraries to implement the specific semantics of their DSL. Since semantic restrictions like method definiton are unimportant, the syntax of your DSL can be quite free.\n\nNote that this library is unstable and experimental. Several severe limitations will be described in following sections.\n\n## Installation\n\nBy [Rubygem](https://rubygems.org/gems/resyma):\n\n```shell\n$ gem install resyma\n```\n\nOr by bundle:\n\n```ruby\ngem 'resyma', '~\u003e 0.1.2'\n```\n\n## Define your DSL\n\n**NOTE**: Code using this library should be placed and executed in `.rb` source file, meaning that you cannot try it in REPL like `irb`.\n\nDefine a new DSL by defining a subclass of `Resyma::Language`.\n\n```ruby\nclass MyLang \u003c\u003c Resyma::Language\n  def syntax\n    regex \u003e\u003e action\n    more...\n  end\nend\n```\n\n`regex` is a DSL defining syntax of your language, and `action` is an arbitrary ruby expression defining semantics of your language. In particular, `regex` is one of following:\n\n- `type`, `type(\"value\")`, `\"value\"`: match a node by type, value, or both.\n- `(a; b; c)`: match a sequence of nodes in order of `a`, `b`, `c`, where every component is a `regex`\n- `[a, b, c]`: match one of `a`, `b`, `c`, where every component is a `regex`\n- `a..`, `a...`: match `a` zero or more time, or one or more time, where `a` is a `regex`\n- `[a]`: optionally match `a`\n\nComprehensive document is in the plan.\n\n## Limitations\n\n- Parse tree, not AST\n\n  Our algorithm works on parse trees, namely concrete syntax trees, but not AST. However, most of Ruby libraries function only at the AST level. Currently, we derive AST by [parser](https://github.com/whitequark/parser) and convert it to parse tree. It is an unacceptable solution because AST of `parser` describes abstract structures of codes and disregards details like parenthesis or semicolons, which in turn causes malfunction of our algorithm.\n- Capturing group\n\n  In regular expression of string, we capture key components by grouping (e.g., `/Hi, (\\w+)!/`) for further processing. Without this feature, regular expression is just a boolean function and almost useless. Currently, `Resyma` does not support capturing group, but we can provide users with a complete list of nodes matched with the regular expression. So users can process matched nodes but cannot choose specific nodes.\n\n## More examples\n\n### Nise-TOML\n\n[TOML](https://toml.io/en/) is a configuraton language.\n\n```ruby\nrequire \"resyma/nise/toml\"\n\nLangTOML.load do\n\n    # This is a nise-TOML document\n\n    title = \"TOML Example\"\n\n    [owner]\n    name = \"Tom Preston-Werner\"\n\n    [database]\n    enabled = true\n    ports = [ 8000, 8001, 8002 ]\n    data = [ [\"delta\", \"phi\"], [3.14] ]\n    temp_targets = { cpu: 79.5, case: 72.0 }\n\n    [servers]\n\n    [servers.alpha]\n    ip = \"10.0.0.1\"\n    role = \"frontend\"\n\n    [servers.beta]\n    ip = \"10.0.0.2\"\n    role = \"backend\"\nend\n\n#=\u003e   {:title    =\u003e \"TOML Example\",\n#      :owner    =\u003e {:name =\u003e \"Tom Preston-Werner\"},\n#      :database =\u003e {:enabled =\u003e true,\n#                    :ports   =\u003e [8000, 8001, 8002],\n#                    :data    =\u003e [[\"delta\", \"phi\"], [3.14]],\n#                    :temp_targets =\u003e{:cpu =\u003e 79.5, :case =\u003e 72.0}},\n#      :servers  =\u003e {:alpha =\u003e {:ip =\u003e \"10.0.0.1\", :role =\u003e \"frontend\"},\n#                    :beta  =\u003e {:ip =\u003e \"10.0.0.2\", :role =\u003e \"backend\"}}}\n```\n\n### Timeline\n\n`Timeline` uses DSL defined by the example at the top.\n\n```ruby\nrequire \"resyma/nise/date\"\n\nLangTimeline.load do\n  [2020/8/15] - \"First day of class\"\n  [2020/10/9] - \"Test #1\"\n  [yesterday] - \"Research paper due\"\n  [today]     - \"Zzz...\"\n  [+7.day]    - \"Test #2\"\n  [+2.month]  - \"Final project due\"\nend\n\n#=\u003e [[#\u003cDate: 2020-08-15 (...)\u003e, \"First day of class\"],\n#    [#\u003cDate: 2020-10-09 (...)\u003e, \"Test #1\"],\n#    [#\u003cDate: 2023-02-08 (...)\u003e, \"Research paper due\"],\n#    [#\u003cDate: 2023-02-09 (...)\u003e, \"Zzz...\"],\n#    [#\u003cDate: 2023-02-16 (...)\u003e, \"Test #2\"],\n#    [#\u003cDate: 2023-04-09 (...)\u003e, \"Final project due\"]]\n```\n\n### Rubymoji\n\n```ruby\nrequire \"resyma/nise/rubymoji\"\n\nrumoji { o^o }         #=\u003e 🙃\nrumoji { O.O ?? }      #=\u003e 🤔\nrumoji { Zzz.. (x.x) } #=\u003e 😴\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdzangfan%2Fresyma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdzangfan%2Fresyma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdzangfan%2Fresyma/lists"}