{"id":22127244,"url":"https://github.com/floraison/raabro","last_synced_at":"2025-04-05T21:08:45.774Z","repository":{"id":2909409,"uuid":"42791054","full_name":"floraison/raabro","owner":"floraison","description":"a Ruby PEG parser library","archived":false,"fork":false,"pushed_at":"2024-09-06T01:58:35.000Z","size":148,"stargazers_count":38,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-31T13:19:39.028Z","etag":null,"topics":["parser","peg","ruby"],"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/floraison.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2015-09-19T21:56:24.000Z","updated_at":"2025-01-23T11:01:31.000Z","dependencies_parsed_at":"2024-09-17T06:18:10.931Z","dependency_job_id":null,"html_url":"https://github.com/floraison/raabro","commit_stats":{"total_commits":222,"total_committers":6,"mean_commits":37.0,"dds":0.036036036036036,"last_synced_commit":"b34be383e0bbdd39f4bb8ee0bfc82365b838014e"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fraabro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fraabro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fraabro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floraison%2Fraabro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/floraison","download_url":"https://codeload.github.com/floraison/raabro/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247056263,"owners_count":20876364,"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":["parser","peg","ruby"],"created_at":"2024-12-01T17:10:51.152Z","updated_at":"2025-04-05T21:08:45.768Z","avatar_url":"https://github.com/floraison.png","language":"Ruby","readme":"\n# raabro\n\n[![tests](https://github.com/floraison/raabro/workflows/test/badge.svg)](https://github.com/floraison/raabro/actions)\n[![Gem Version](https://badge.fury.io/rb/raabro.svg)](http://badge.fury.io/rb/raabro)\n\nA very dumb PEG parser library.\n\nSon to [aabro](https://github.com/flon-io/aabro), grandson to [neg](https://github.com/jmettraux/neg), grand-grandson to [parslet](https://github.com/kschiess/parslet). There is also a javascript version [jaabro](https://github.com/jmettraux/jaabro).\n\n\n## a sample parser/rewriter\n\nYou use raabro by providing the parsing rules, then some rewrite rules.\n\nThe parsing rules make use of the raabro basic parsers `seq`, `alt`, `str`, `rex`, `eseq`, ...\n\nThe rewrite rules match names passed as first argument to the basic parsers to rewrite the resulting parse trees.\n\n```ruby\nrequire 'raabro'\n\n\nmodule Fun include Raabro\n\n  # parse\n  #\n  # Last function is the root, \"i\" stands for \"input\".\n\n  def pstart(i); rex(nil, i, /\\(\\s*/); end\n  def pend(i); rex(nil, i, /\\)\\s*/); end\n    # parentheses start and end, including trailing white space\n\n  def comma(i); rex(nil, i, /,\\s*/); end\n    # a comma, including trailing white space\n\n  def num(i); rex(:num, i, /-?[0-9]+\\s*/); end\n    # name is :num, a positive or negative integer\n\n  def args(i); eseq(nil, i, :pstart, :exp, :comma, :pend); end\n    # a set of :exp, beginning with a (, punctuated by commas and ending with )\n\n  def funame(i); rex(nil, i, /[a-z][a-z0-9]*/); end\n  def fun(i); seq(:fun, i, :funame, :args); end\n    # name is :fun, a function composed of a function name\n    # followed by arguments\n\n  def exp(i); alt(nil, i, :fun, :num); end\n    # an expression is either (alt) a function or a number\n\n  # rewrite\n  #\n  # Names above (:num, :fun, ...) get a rewrite_xxx function.\n  # \"t\" stands for \"tree\".\n\n  def rewrite_exp(t); rewrite(t.children[0]); end\n  def rewrite_num(t); t.string.to_i; end\n\n  def rewrite_fun(t)\n\n    funame, args = t.children\n\n    [ funame.string ] +\n    args.gather.collect { |e| rewrite(e) }\n      #\n      # #gather collect all the children in a tree that have\n      # a name, in this example, names can be :exp, :num, :fun\n  end\nend\n\n\np Fun.parse('mul(1, 2)')\n  # =\u003e [\"mul\", 1, 2]\n\np Fun.parse('mul(1, add(-2, 3))')\n  # =\u003e [\"mul\", 1, [\"add\", -2, 3]]\n\np Fun.parse('mul (1, 2)')\n  # =\u003e nil (doesn't accept a space after the function name)\n```\n\nThis sample is available at: [doc/readme0.rb](doc/readme0.rb).\n\n## custom rewrite()\n\nBy default, a parser gets a `rewrite(t)` that looks at the parse tree node names and calls the corresponding `rewrite_{node_name}()`.\n\nIt's OK to provide a custom `rewrite(t)` function.\n\n```ruby\nmodule Hello include Raabro\n\n  def hello(i); str(:hello, i, 'hello'); end\n\n  def rewrite(t)\n    [ :ok, t.string ]\n  end\nend\n```\n\n\n## basic parsers\n\nOne makes a parser by composing basic parsers, for example:\n```ruby\n  def args(i); eseq(:args, i, :pa, :exp, :com, :pz); end\n  def funame(i); rex(:funame, i, /[a-z][a-z0-9]*/); end\n  def fun(i); seq(:fun, i, :funame, :args); end\n```\nwhere the `fun` parser is a sequence combining the `funame` parser then the `args` one. `:fun` (the first argument to the basic parser `seq`) will be the name of the resulting (local) parse tree.\n\nBelow is a list of the basic parsers provided by Raabro.\n\nThe first parameter to the basic parser is the name used by rewrite rules.\nThe second parameter is a `Raabro::Input` instance, mostly a wrapped string.\n\n```ruby\ndef str(name, input, string)\n  # matching a string\n\ndef rex(name, input, regex_or_string)\n  # matching a regexp\n  # no need for ^ or \\A, checks the match occurs at current offset\n\ndef blk(name, input, \u0026block)\n  #\n  # takes a block returning nil or false when it doesn't match or the\n  # length of the matching string if it matches\n  #\n  # the block take 0, 1 [ string_from_parse_point ], or 2 [ str, input ]\n  # arguments\n\ndef seq(name, input, *parsers)\n  # a sequence of parsers\n\ndef alt(name, input, *parsers)\n  # tries the parsers returns as soon as one succeeds\n\ndef altg(name, input, *parsers)\n  # tries all the parsers, returns with the longest match\n\ndef rep(name, input, parser, min, max=0)\n  # repeats the the wrapped parser\n\ndef nott(name, input, parser)\n  # succeeds if the wrapped parser fails, fails if it succeeds\n\ndef ren(name, input, parser)\n  # renames the output of the wrapped parser\n\ndef jseq(name, input, eltpa, seppa)\n  #\n  # seq(name, input, eltpa, seppa, eltpa, seppa, eltpa, seppa, ...)\n  #\n  # a sequence of `eltpa` parsers separated (joined) by `seppa` parsers\n\ndef eseq(name, input, startpa, eltpa, seppa, endpa)\n  #\n  # seq(name, input, startpa, eltpa, seppa, eltpa, seppa, ..., endpa)\n  #\n  # a sequence of `eltpa` parsers separated (joined) by `seppa` parsers\n  # preceded by a `startpa` parser and followed by a `endpa` parser\n```\n\n\n## the `seq` parser and its quantifiers\n\n`seq` is special, it understands \"quantifiers\": `'?'`, `'+'` or `'*'`. They make behave `seq` a bit like a classical regex.\n\nThe `'!'` (bang, not) quantifier is explained at the end of this section.\n\n```ruby\nmodule CartParser include Raabro\n\n  def fruit(i)\n    rex(:fruit, i, /(tomato|apple|orange)/)\n  end\n  def vegetable(i)\n    rex(:vegetable, i, /(potato|cabbage|carrot)/)\n  end\n\n  def cart(i)\n    seq(:cart, i, :fruit, '*', :vegetable, '*')\n  end\n    # zero or more fruits followed by zero or more vegetables\nend\n```\n\n(Yes, this sample parser parses string like \"appletomatocabbage\", it's not very useful, but I hope you get the point about `.seq`)\n\nThe `'!'` (bang, not) quantifier is a kind of \"negative lookahead\".\n\n```ruby\n  def menu(i)\n    seq(:menu, i, :mise_en_bouche, :main, :main, '!', :dessert)\n  end\n```\n\nLousy example, but here a main cannot follow a main.\n\n\n## trees\n\nAn instance of `Raabro::Tree` is passed to `rewrite()` and `rewrite_{name}()` functions.\n\nThe most useful methods of this class are:\n```ruby\nclass Raabro::Tree\n\n  # Look for the first child or sub-child with the given name.\n  # If the given name is nil, looks for the first child with a name (not nil).\n  #\n  def sublookup(name=nil)\n\n  # Gathers all the children or sub-children with the given name.\n  # If the given name is nil, gathers all the children with a name (not nil).\n  # When a child matches, does not pursue gathering from the children of the\n  # matching child.\n  #\n  def subgather(name=nil)\nend\n```\n\nI'm using \"child or sub-child\" instead of \"descendant\" because once a child or sub-child matches, those methods do not consider the children or sub-children of that matching entity.\n\nHere is a closeup on the rewrite functions of the sample parser at [doc/readme1.rb](doc/readme1.rb) (extracted from an early version of [floraison/dense](https://github.com/floraison/dense)):\n```ruby\nrequire 'raabro'\n\nmodule PathParser include Raabro\n\n  # (...)\n\n  def rewrite_name(t); t.string; end\n  def rewrite_off(t); t.string.to_i; end\n  def rewrite_index(t); rewrite(t.sublookup); end\n  def rewrite_path(t); t.subgather(:index).collect { |tt| rewrite(tt) }; end\nend\n```\nWhere `rewrite_index(t)` returns the result of the rewrite of the first of its children that has a name and `rewrite_path(t)` collects the result of the rewrite of all of its children that have the \"index\" name.\n\n\n## errors\n\nBy default, a parser will return nil when it cannot successfully parse the input.\n\nFor example, given the above [`Fun` parser](#a-sample-parserrewriter), parsing some truncated input would yield `nil`:\n```ruby\ntree = Sample::Fun.parse('f(a, b')\n  # yields `nil`...\n```\n\nOne can reparse with `error: true` and receive an error array with the parse error details:\n```ruby\nerr = Sample::Fun.parse('f(a, b', error: true)\n  # yields:\n  # [ line, column, offset, error_message, error_visual ]\n[ 1, 4, 3, 'parsing failed .../:exp/:fun/:arg', \"f(a, b\\n   ^---\" ]\n```\n\nThe last string in the error array looks like when printed out:\n```\nf(a, b\n   ^---\n```\n\n### error when not all is consumed\n\nConsider the following toy parser:\n```ruby\nmodule ToPlus include Raabro\n\n  # parse\n\n  def to_plus(input); rep(:tos, input, :to, 1); end\n\n  # rewrite\n\n  def rewrite(t); [ :ok, t.string ]; end\nend\n```\n\n```ruby\nSample::ToPlus.parse('totota')\n  # yields nil since all the input was not parsed, \"ta\" is remaining\n\nSample::ToPlus.parse('totota', all: false)\n  # yields\n[ :ok, \"toto\" ]\n  # and doesn't care about the remaining input \"ta\"\n\nSample::ToPlus.parse('totota', error: true)\n  # yields\n[ 1, 5, 4, \"parsing failed, not all input was consumed\", \"totota\\n    ^---\" ]\n```\n\nThe last string in the error array looks like when printed out:\n```\ntotota\n    ^---\n```\n\n\n## LICENSE\n\nMIT, see [LICENSE.txt](LICENSE.txt)\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloraison%2Fraabro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffloraison%2Fraabro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloraison%2Fraabro/lists"}