Git Product home page Git Product logo

citrus's Introduction

Citrus :: Parsing Expressions for Ruby

Citrus is a compact and powerful parsing library for Ruby that combines the elegance and expressiveness of the language with the simplicity and power of parsing expressions.

Installation

Via RubyGems:

$ gem install citrus

From a local copy:

$ git clone git://github.com/mjackson/citrus.git
$ cd citrus
$ rake package install

Background

In order to be able to use Citrus effectively, you must first understand the difference between syntax and semantics. Syntax is a set of rules that govern the way letters and punctuation may be used in a language. For example, English syntax dictates that proper nouns should start with a capital letter and that sentences should end with a period.

Semantics are the rules by which meaning may be derived in a language. For example, as you read a book you are able to make some sense of the particular way in which words on a page are combined to form thoughts and express ideas because you understand what the words themselves mean and you understand what they mean collectively.

Computers use a similar process when interpreting code. First, the code must be parsed into recognizable symbols or tokens. These tokens may then be passed to an interpreter which is responsible for forming actual instructions from them.

Citrus is a pure Ruby library that allows you to perform both lexical analysis and semantic interpretation quickly and easily. Using Citrus you can write powerful parsers that are simple to understand and easy to create and maintain.

In Citrus, there are three main types of objects: rules, grammars, and matches.

Rules

A Rule is an object that specifies some matching behavior on a string. There are two types of rules: terminals and non-terminals. Terminals can be either Ruby strings or regular expressions that specify some input to match. For example, a terminal created from the string "end" would match any sequence of the characters "e", "n", and "d", in that order. Terminals created from regular expressions may match any sequence of characters that can be generated from that expression.

Non-terminals are rules that may contain other rules but do not themselves match directly on the input. For example, a Repeat is a non-terminal that may contain one other rule that will try and match a certain number of times. Several other types of non-terminals are available that will be discussed later.

Rule objects may also have semantic information associated with them in the form of Ruby modules. Rules use these modules to extend the matches they create.

Grammars

A Grammar is a container for rules. Usually the rules in a grammar collectively form a complete specification for some language, or a well-defined subset thereof.

A Citrus grammar is really just a souped-up Ruby module. These modules may be included in other grammar modules in the same way that Ruby modules are normally used. This property allows you to divide a complex grammar into more manageable, reusable pieces that may be combined at runtime. Any rule with the same name as a rule in an included grammar may access that rule with a mechanism similar to Ruby's super keyword.

Matches

A Match object represents a successful recognition of some piece of the input. Matches are created by rule objects during a parse.

Matches are arranged in a tree structure where any match may contain any number of other matches. Each match contains information about its own subtree. The structure of the tree is determined by the way in which the rule that generated each match is used in the grammar. For example, a match that is created from a nonterminal rule that contains several other terminals will likewise contain several matches, one for each terminal. However, this is an implementation detail and should be relatively transparent to the user.

Match objects may be extended with semantic information in the form of methods. These methods should provide various interpretations for the semantic value of a match.

Syntax

The most straightforward way to compose a Citrus grammar is to use Citrus' own custom grammar syntax. This syntax borrows heavily from Ruby, so it should already be familiar to Ruby programmers.

Terminals

Terminals may be represented by a string or a regular expression. Both follow the same rules as Ruby string and regular expression literals.

'abc'         # match "abc"
"abc\n"       # match "abc\n"
/abc/i        # match "abc" in any case
/\xFF/        # match "\xFF"

Character classes and the dot (match anything) symbol are supported as well for compatibility with other parsing expression implementations.

[a-z0-9]      # match any lowercase letter or digit
[\x00-\xFF]   # match any octet
.             # match any single character, including new lines

Also, strings may use backticks instead of quotes to indicate that they should match in a case-insensitive manner.

`abc`         # match "abc" in any case

Besides case sensitivity, case-insensitive strings have the same behavior as double quoted strings.

See Terminal and StringTerminal for more information.

Repetition

Quantifiers may be used after any expression to specify a number of times it must match. The universal form of a quantifier is N*M where N is the minimum and M is the maximum number of times the expression may match.

'abc'1*2      # match "abc" a minimum of one, maximum of two times
'abc'1*       # match "abc" at least once
'abc'*2       # match "abc" a maximum of twice

Additionally, the minimum and maximum may be omitted entirely to specify that an expression may match zero or more times.

'abc'*        # match "abc" zero or more times

The + and ? operators are supported as well for the common cases of 1* and *1 respectively.

'abc'+        # match "abc" one or more times
'abc'?        # match "abc" zero or one time

See Repeat for more information.

Lookahead

Both positive and negative lookahead are supported in Citrus. Use the & and ! operators to indicate that an expression either should or should not match. In neither case is any input consumed.

'a' &'b'      # match an "a" that is followed by a "b"
'a' !'b'      # match an "a" that is not followed by a "b"
!'a' .        # match any character except for "a"

A special form of lookahead is also supported which will match any character that does not match a given expression.

~'a'          # match all characters until an "a"
~/xyz/        # match all characters until /xyz/ matches

When using this operator (the tilde), at least one character must be consumed for the rule to succeed.

See AndPredicate, NotPredicate, and ButPredicate for more information.

Sequences

Sequences of expressions may be separated by a space to indicate that the rules should match in that order.

'a' 'b' 'c'   # match "a", then "b", then "c"
'a' [0-9]     # match "a", then a numeric digit

See Sequence for more information.

Choices

Ordered choice is indicated by a vertical bar that separates two expressions. When using choice, each expression is tried in order. When one matches, the rule returns the match immediately without trying the remaining rules.

'a' | 'b'       # match "a" or "b"
'a' 'b' | 'c'   # match "a" then "b" (in sequence), or "c"

It is important to note when using ordered choice that any operator binds more tightly than the vertical bar. A full chart of operators and their respective levels of precedence is below.

See Choice for more information.

Labels

Match objects may be referred to by a different name than the rule that originally generated them. Labels are added by placing the label and a colon immediately preceding any expression.

chars:/[a-z]+/  # the characters matched by the regular expression
                # may be referred to as "chars" in an extension
                # method

Extensions

Extensions may be specified using either "module" or "block" syntax. When using module syntax, specify the name of a module that is used to extend match objects in between less than and greater than symbols.

[a-z0-9]5*9 <CouponCode>  # match a string that consists of any lower
                          # cased letter or digit between 5 and 9
                          # times and extend the match with the
                          # CouponCode module

Additionally, extensions may be specified inline using curly braces. When using this method, the code inside the curly braces may be invoked by calling the value method on the match object.

[0-9] { to_str.to_i }     # match any digit and return its integer value when
                          # calling the #value method on the match object

Note that when using the inline block method you may also specify arguments in between vertical bars immediately following the opening curly brace, just like in Ruby blocks.

Super

When including a grammar inside another, all rules in the child that have the same name as a rule in the parent also have access to the super keyword to invoke the parent rule.

grammar Number
  rule number
    [0-9]+
  end
end

grammar FloatingPoint
  include Number

  rule number
    super ('.' super)?
  end
end

In the example above, the FloatingPoint grammar includes Number. Both have a rule named number, so FloatingPoint#number has access to Number#number by means of using super.

See Super for more information.

Precedence

The following table contains a list of all Citrus symbols and operators and their precedence. A higher precedence indicates tighter binding.

Operator Name Precedence
'' String (single quoted) 7
"" String (double quoted) 7
`` String (case insensitive) 7
[] Character class 7
. Dot (any character) 7
// Regular expression 7
() Grouping 7
* Repetition (arbitrary) 6
+ Repetition (one or more) 6
? Repetition (zero or one) 6
& And predicate 5
! Not predicate 5
~ But predicate 5
<> Extension (module name) 4
{} Extension (literal) 4
: Label 3
e1 e2 Sequence 2
e1 | e2 Ordered choice 1

Grouping

As is common in many programming languages, parentheses may be used to override the normal binding order of operators. In the following example parentheses are used to make the vertical bar between 'b' and 'c' bind tighter than the space between 'a' and 'b'.

'a' ('b' | 'c')   # match "a", then "b" or "c"

Example

Below is an example of a simple grammar that is able to parse strings of integers separated by any amount of white space and a + symbol.

grammar Addition
  rule additive
    number plus (additive | number)
  end

  rule number
    [0-9]+ space
  end

  rule plus
    '+' space
  end

  rule space
    [ \t]*
  end
end

Several things to note about the above example:

  • Grammar and rule declarations end with the end keyword
  • A sequence of rules is created by separating expressions with a space
  • Likewise, ordered choice is represented with a vertical bar
  • Parentheses may be used to override the natural binding order
  • Rules may refer to other rules in their own definitions simply by using the other rule's name
  • Any expression may be followed by a quantifier

Interpretation

The grammar above is able to parse simple mathematical expressions such as "1+2" and "1 + 2+3", but it does not have enough semantic information to be able to actually interpret these expressions.

At this point, when the grammar parses a string it generates a tree of Match objects. Each match is created by a rule and may itself be comprised of any number of submatches.

Submatches are created whenever a rule contains another rule. For example, in the grammar above number matches a string of digits followed by white space. Thus, a match generated by this rule will contain two submatches.

We can define a method inside a set of curly braces that will be used to extend a particular rule's matches. This works in similar fashion to using Ruby's blocks. Let's extend the Addition grammar using this technique.

grammar Addition
  rule additive
    (number plus term:(additive | number)) {
      capture(:number).value + capture(:term).value
    }
  end

  rule number
    ([0-9]+ space) {
      to_str.to_i
    }
  end

  rule plus
    '+' space
  end

  rule space
    [ \t]*
  end
end

In this version of the grammar we have added two semantic blocks, one each for the additive and number rules. These blocks contain code that we can execute by calling value on match objects that result from those rules. It's easiest to explain what is going on here by starting with the lowest level block, which is defined within number.

Inside this block we see a call to another method, namely to_str. When called in the context of a match object, this method returns the match's internal string object. Thus, the call to to_str.to_i should return the integer value of the match.

Similarly, matches created by additive will also have a value method. Notice the use of the term label within the rule definition. This label allows the match that is created by the choice between additive and number to be retrieved using capture(:term). The value of an additive match is determined to be the values of its number and term matches added together using Ruby's addition operator. Note that the plural form captures(:term) can be used to get an array of matches for a given label (e.g. when the label belongs to a repetition).

Since additive is the first rule defined in the grammar, any match that results from parsing a string with this grammar will have a value method that can be used to recursively calculate the collective value of the entire match tree.

To give it a try, save the code for the Addition grammar in a file called addition.citrus. Next, assuming you have the Citrus gem installed, try the following sequence of commands in a terminal.

$ irb
> require 'citrus'
 => true
> Citrus.load 'addition'
 => [Addition]
> m = Addition.parse '1 + 2 + 3'
 => #<Citrus::Match ...
> m.value
 => 6

Congratulations! You just ran your first piece of Citrus code.

One interesting thing to notice about the above sequence of commands is the return value of Citrus#load. When you use Citrus.load to load a grammar file (and likewise Citrus#eval to evaluate a raw string of grammar code), the return value is an array of all the grammars present in that file.

Take a look at calc.citrus for an example of a calculator that is able to parse and evaluate more complex mathematical expressions.

Additional Methods

If you need more than just a value method on your match object, you can attach additional methods as well. There are two ways to do this. The first lets you define additional methods inline in your semantic block. This block will be used to create a new Module using Module#new. Using the Addition example above, we might refactor the additive rule to look like this:

rule additive
  (number plus term:(additive | number)) {
    def lhs
      capture(:number).value
    end

    def rhs
      capture(:term).value
    end

    def value
      lhs + rhs
    end
  }
end

Now, in addition to having a value method, matches that result from the additive rule will have a lhs and a rhs method as well. Although not particularly useful in this example, this technique can be useful when unit testing more complex rules. For example, using this method you might make the following assertions in a unit test:

match = Addition.parse('1 + 4')
assert_equal(1, match.lhs)
assert_equal(4, match.rhs)
assert_equal(5, match.value)

If you would like to abstract away the code in a semantic block, simply create a separate Ruby module (in another file) that contains the extension methods you want and use the angle bracket notation to indicate that a rule should use that module when extending matches.

To demonstrate this method with the above example, in a Ruby file you would define the following module.

module Additive
  def lhs
    capture(:number).value
  end

  def rhs
    capture(:term).value
  end

  def value
    lhs + rhs
  end
end

Then, in your Citrus grammar file the rule definition would look like this:

  rule additive
    (number plus term:(additive | number)) <Additive>
  end

This method of defining extensions can help keep your grammar files cleaner. However, you do need to make sure that your extension modules are already loaded before using Citrus.load to load your grammar file.

Testing

Citrus was designed to facilitate simple and powerful testing of grammars. To demonstrate how this is to be done, we'll use the Addition grammar from our previous example. The following code demonstrates a simple test case that could be used to test that our grammar works properly.

class AdditionTest < Test::Unit::TestCase
  def test_additive
    match = Addition.parse('23 + 12', :root => :additive)
    assert(match)
    assert_equal('23 + 12', match)
    assert_equal(35, match.value)
  end

  def test_number
    match = Addition.parse('23', :root => :number)
    assert(match)
    assert_equal('23', match)
    assert_equal(23, match.value)
  end
end

The key here is using the :root option when performing the parse to specify the name of the rule at which the parse should start. In test_number, since :number was given the parse will start at that rule as if it were the root rule of the entire grammar. The ability to change the root rule on the fly like this enables easy unit testing of the entire grammar.

Also note that because match objects are themselves strings, assertions may be made to test equality of match objects with string values.

Debugging

When a parse fails, a ParseError object is generated which provides a wealth of information about exactly where the parse failed including the offset, line number, line text, and line offset. Using this object, you could possibly provide some useful feedback to the user about why the input was bad. The following code demonstrates one way to do this.

def parse_some_stuff(stuff)
  match = StuffGrammar.parse(stuff)
rescue Citrus::ParseError => e
  raise ArgumentError, "Invalid stuff on line %d, offset %d!" %
    [e.line_number, e.line_offset]
end

In addition to useful error objects, Citrus also includes a means of visualizing match trees in the console via Match#dump. This can help when determining which rules are generating which matches and how they are organized in the match tree.

Extras

Several files are included in the Citrus repository that make it easier to work with grammar files in various editors.

TextMate

To install the Citrus TextMate bundle, simply double-click on the Citrus.tmbundle file in the extras directory.

Vim

To install the Vim scripts, copy the files in extras/vim to a directory in Vim's runtimepath.

Examples

The project source directory contains several example scripts that demonstrate how grammars are to be constructed and used. Each Citrus file in the examples directory has an accompanying Ruby file that contains a suite of tests for that particular file.

The best way to run any of these examples is to pass the name of the Ruby file directly to the Ruby interpreter on the command line, e.g.:

$ ruby -Ilib examples/calc_test.rb

This particular invocation uses the -I flag to ensure that you are using the version of Citrus that was bundled with that particular example file (i.e. the version that is contained in the lib directory).

Links

Discussion around Citrus happens on the citrus-users Google group.

The primary resource for all things to do with parsing expressions can be found on the original Packrat and Parsing Expression Grammars page at MIT.

Also, a useful summary of parsing expression grammars can be found on Wikipedia.

Citrus draws inspiration from another Ruby library for writing parsing expression grammars, Treetop. While Citrus' syntax is similar to that of Treetop, it's not identical. The link is included here for those who may wish to explore an alternative implementation.

License

Copyright 2010-2011 Michael Jackson

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.

citrus's People

Contributors

andrew avatar blambeau avatar cjheath avatar emancu avatar joachimm avatar kylc avatar mjackson avatar mwilden avatar tbuehlmann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

citrus's Issues

Rules don't respond to their own name?

grammar Foo

# The value of digit cannot be retrived because digit is undefined
# which seems odd, especially contrasted with the next rule, below
rule bad
    digit { 100 * digit.value }
end

# The value of digit can be retrieved in this case
rule good
    (digit !'xxx') { 100 * digit.value }
end

# The value of thing cannot be retrived because thing is undefined
# which seems odd, especially contrasted with the next rule, below
rule bad2
    thing:digit { 100 * thing.value }
end

# The value of thing can be retrieved in this case
rule good2
    (thing:digit !'xxx') { 100 * thing.value }
end

rule digit
[0-9]
end

end

Custom Rule (ex Lemmatization)

First of all, awesome work !! I think citrus is very close to pyparsing.

Any idea how I can implement a custom parsing Rule, let's say for lemmatization?

-Cheers
Deb

Enforcing balanced parentheses.

In the addition example, how would you specify paren balancing such as "1 + (2 / 3)"? The documentation allows for using a "&" for look-aheads, but can this be done when there are tokens between the opening and closing paren?

Thanks much for a great library!

Preston

Display parse matches or state on ParseError

I'm really new to PEGs so I'm sure I'm writing some totally messed up grammars. When the parse succeeds, I'm able to tweak things because there's a very helpful dump that shows me exactly the process the parser used to group everything together. The problem lies when the parsing fails. Although a line number and offset is very helpful, I'm unclear on exactly which rule the parser was currently in when it failed, etc.

I would really love some kind of character-by-character dump showing the entire process of what was going on. It would really help clear up all of the stupid mistakes I'm sure I'm making. In other words, I basically want to see the match dump regardless of whether the parsing succeeds.

Is this a challenging feature to add?

Segfault in Citrus during toml-rb initialization with Ruby 3.0

I get a segfault sporadically when I'm requiring toml-rb. I figured out that this happens when the toml-rb code is loading some Citrus grammars. I report this issue to Citrus as this seems to be incorrect behavior regardless the content of those grammars.

This is what toml-rb is doing:

require 'citrus'
...
ROOT = File.dirname(File.expand_path(__FILE__))
Citrus.load "#{ROOT}/toml-rb/grammars/helper.citrus"
Citrus.load "#{ROOT}/toml-rb/grammars/primitive.citrus"
Citrus.load "#{ROOT}/toml-rb/grammars/array.citrus"
Citrus.load "#{ROOT}/toml-rb/grammars/document.citrus"

In those cases when I bothered to check the crash data, the crash occurred when loading of array.citrus was attempted; I'll post an update if I will find it occuring in another Citrus.load call.

I'm aware of this behavior with Ruby 3.0.x; I'm not sure if it has ever occurred with Ruby 2.x, but I think it hasn't. As this happens during an invocation of a script in an interactive shell, a plausible workaround is to re-run the command, which usually succeeds.

I'm on Arch Linux x86_64, package versions:

> ~/python/scratch/pacmandump.py | ruby -rjson -ryaml -ne 'JSON.load($_).then { |r| r["name"] =~ /\Aruby(-(citrus|toml-rb))?\Z/ and YAML.dump %w[name version packager].map { |k| [k, r[k]] }.to_h, $>}' 
---
name: ruby
version: 3.0.1-1
packager: Anatol Pomozov <[email protected]>
---
name: ruby-citrus
version: 3.0.2-1
packager: Unknown Packager
---
name: ruby-toml-rb
version: 2.0.1-1
packager: Unknown Packager

(Unknown Packager in Arch parlance means "unofficial package").

Crash data:

/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1286: [BUG] Segmentation fault at 0x000055e82611d000
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0055 p:---- s:0291 e:000290 CFUNC  :slice!
c:0054 p:0063 s:0285 e:000284 METHOD /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1286 [FINISH]
c:0053 p:---- s:0276 e:000275 CFUNC  :new
c:0052 p:0139 s:0269 e:000268 METHOD /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1470
c:0051 p:0011 s:0254 e:000253 METHOD /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1330
c:0050 p:0003 s:0249 e:000248 METHOD /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1336
c:0049 p:0006 s:0244 e:000243 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:130 [FINISH]
c:0048 p:0015 s:0239 e:000238 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:122 [FINISH]
c:0047 p:0015 s:0234 e:000233 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:113 [FINISH]
c:0046 p:0015 s:0229 e:000228 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:104 [FINISH]
c:0045 p:0005 s:0224 e:000223 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96 [FINISH]
c:0044 p:---- s:0220 e:000219 CFUNC  :map
c:0043 p:0011 s:0216 e:000215 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96 [FINISH]
c:0042 p:0005 s:0212 e:000211 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89 [FINISH]
c:0041 p:---- s:0208 e:000207 CFUNC  :map
c:0040 p:0011 s:0204 e:000203 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89 [FINISH]
c:0039 p:0008 s:0200 e:000199 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:143 [FINISH]
c:0038 p:0015 s:0197 e:000196 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:131 [FINISH]
c:0037 p:0015 s:0192 e:000191 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:122 [FINISH]
c:0036 p:0015 s:0187 e:000186 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:113 [FINISH]
c:0035 p:0015 s:0182 e:000181 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:104 [FINISH]
c:0034 p:0005 s:0177 e:000176 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96 [FINISH]
c:0033 p:---- s:0173 e:000172 CFUNC  :map
c:0032 p:0011 s:0169 e:000168 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96 [FINISH]
c:0031 p:0005 s:0165 e:000164 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89 [FINISH]
c:0030 p:---- s:0161 e:000160 CFUNC  :map
c:0029 p:0011 s:0157 e:000156 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89 [FINISH]
c:0028 p:0008 s:0153 e:000152 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:143 [FINISH]
c:0027 p:0015 s:0150 e:000149 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:131 [FINISH]
c:0026 p:0015 s:0145 e:000144 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:122 [FINISH]
c:0025 p:0015 s:0140 e:000139 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:113 [FINISH]
c:0024 p:0015 s:0135 e:000134 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:104 [FINISH]
c:0023 p:0005 s:0130 e:000129 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96 [FINISH]
c:0022 p:---- s:0126 e:000125 CFUNC  :map
c:0021 p:0011 s:0122 e:000121 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96 [FINISH]
c:0020 p:0005 s:0118 e:000117 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89 [FINISH]
c:0019 p:---- s:0114 e:000113 CFUNC  :map
c:0018 p:0011 s:0110 e:000109 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89 [FINISH]
c:0017 p:0015 s:0106 e:000105 METHOD /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:82
c:0016 p:0013 s:0101 e:000098 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:63 [FINISH]
c:0015 p:---- s:0095 e:000094 CFUNC  :each
c:0014 p:0043 s:0091 e:000090 METHOD /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:63
c:0013 p:0005 s:0085 e:000084 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:51 [FINISH]
c:0012 p:---- s:0081 e:000080 CFUNC  :map
c:0011 p:0023 s:0077 e:000076 BLOCK  /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:51 [FINISH]
c:0010 p:0021 s:0074 e:000073 METHOD /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:47
c:0009 p:0070 s:0068 e:000064 METHOD /usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:77
c:0008 p:0195 s:0057 e:000056 TOP    /usr/lib/ruby/gems/3.0.0/gems/toml-rb-2.0.1/lib/toml-rb.rb:17 [FINISH]
c:0007 p:---- s:0054 e:000053 CFUNC  :require
c:0006 p:0081 s:0049 e:000048 RESCUE <internal:/usr/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:160
c:0005 p:0677 s:0045 e:000044 METHOD <internal:/usr/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:149
c:0004 p:0029 s:0028 e:000027 TOP    /home/csaba/ruby/bzhttp.rb:7 [FINISH]
c:0003 p:---- s:0025 e:000024 CFUNC  :require
c:0002 p:0195 s:0020 e:000019 METHOD <internal:/usr/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85 [FINISH]
c:0001 p:0000 s:0003 E:001fa0 (none) [FINISH]

-- Ruby level backtrace information ----------------------------------------
<internal:/usr/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
<internal:/usr/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
/home/csaba/ruby/bzhttp.rb:7:in `<top (required)>'
<internal:/usr/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:149:in `require'
<internal:/usr/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:160:in `rescue in require'
<internal:/usr/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:160:in `require'
/usr/lib/ruby/gems/3.0.0/gems/toml-rb-2.0.1/lib/toml-rb.rb:17:in `<top (required)>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:77:in `load'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:47:in `eval'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:51:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:51:in `map'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:51:in `block (4 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:63:in `value'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:63:in `each'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:63:in `block in value'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:82:in `value'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `map'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `block (4 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `map'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `block (4 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:104:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:113:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:122:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:131:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:143:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `map'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `block (4 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `map'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `block (4 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:104:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:113:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:122:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:131:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:143:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `map'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:89:in `block (4 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `map'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:96:in `block (4 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:104:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:113:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:122:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus/file.rb:130:in `block (3 levels) in <module:Citrus>'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1336:in `capture'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1330:in `captures'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1470:in `process_events!'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1470:in `new'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1286:in `initialize'
/usr/lib/ruby/gems/3.0.0/gems/citrus-3.0.2/lib/citrus.rb:1286:in `slice!'

-- Machine register context ------------------------------------------------
 RIP: 0x00007fdad6fa85f5 RBP: 0x000055e825fca090 RSP: 0x00007ffdaa481128
 RAX: 0x00007fdad64afb90 RBX: 0x0000000000000011 RCX: 0x0000000000000000
 RDX: 0x0000000000000088 RDI: 0x00007fdad64afb90 RSI: 0x000055e82611cfc0
  R8: 0x000055e825fca090  R9: 0x0000000000000480 R10: 0x000055e825fca090
 R11: 0x0000000000000005 R12: 0x0000000000000011 R13: 0x0000000000000000
 R14: 0x000055e82611cfc0 R15: 0x000055e8258023b0 EFL: 0x0000000000010202

-- C level backtrace information -------------------------------------------
/usr/lib/libruby.so.3.0(0x7fdad7262f24) [0x7fdad7262f24]
/usr/lib/libruby.so.3.0(0x7fdad70b6892) [0x7fdad70b6892]
/usr/lib/libruby.so.3.0(0x7fdad71d7daa) [0x7fdad71d7daa]
/usr/lib/libc.so.6(__restore_rt+0x0) [0x7fdad6e80f80]
/usr/lib/libc.so.6(0x1645f5) [0x7fdad6fa85f5]
/usr/lib/libruby.so.3.0(0x7fdad704814d) [0x7fdad704814d]
/usr/lib/libruby.so.3.0(rb_ary_tmp_new_from_values+0x43) [0x7fdad7049453]
/usr/lib/libruby.so.3.0(0x7fdad704c7bb) [0x7fdad704c7bb]
/usr/lib/libruby.so.3.0(0x7fdad705289c) [0x7fdad705289c]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(0x7fdad725910b) [0x7fdad725910b]
/usr/lib/libruby.so.3.0(rb_funcallv_kw+0x52) [0x7fdad7259c42]
/usr/lib/libruby.so.3.0(rb_class_new_instance_pass_kw+0x52) [0x7fdad7156862]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_yield+0x78) [0x7fdad7256b48]
/usr/lib/libruby.so.3.0(0x7fdad704b77d) [0x7fdad704b77d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d43b) [0x7fdad724d43b]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_yield+0x78) [0x7fdad7256b48]
/usr/lib/libruby.so.3.0(0x7fdad704b77d) [0x7fdad704b77d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d43b) [0x7fdad724d43b]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_yield+0x78) [0x7fdad7256b48]
/usr/lib/libruby.so.3.0(0x7fdad704b77d) [0x7fdad704b77d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d43b) [0x7fdad724d43b]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_yield+0x78) [0x7fdad7256b48]
/usr/lib/libruby.so.3.0(0x7fdad704b77d) [0x7fdad704b77d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d43b) [0x7fdad724d43b]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_yield+0x78) [0x7fdad7256b48]
/usr/lib/libruby.so.3.0(0x7fdad704b77d) [0x7fdad704b77d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d43b) [0x7fdad724d43b]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_yield+0x78) [0x7fdad7256b48]
/usr/lib/libruby.so.3.0(0x7fdad704b77d) [0x7fdad704b77d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d43b) [0x7fdad724d43b]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_yield+0x78) [0x7fdad7256b48]
/usr/lib/libruby.so.3.0(rb_ary_each+0x3d) [0x7fdad7044d6d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d43b) [0x7fdad724d43b]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_yield+0x78) [0x7fdad7256b48]
/usr/lib/libruby.so.3.0(0x7fdad704b77d) [0x7fdad704b77d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d43b) [0x7fdad724d43b]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_vm_invoke_bmethod+0x249) [0x7fdad7253599]
/usr/lib/libruby.so.3.0(0x7fdad7253bf7) [0x7fdad7253bf7]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(0x7fdad710a0d8) [0x7fdad710a0d8]
/usr/lib/libruby.so.3.0(rb_require_string+0x2d) [0x7fdad710b16d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad7255049) [0x7fdad7255049]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(0x7fdad710a0d8) [0x7fdad710a0d8]
/usr/lib/libruby.so.3.0(rb_require_string+0x2d) [0x7fdad710b16d]
/usr/lib/libruby.so.3.0(0x7fdad72418af) [0x7fdad72418af]
/usr/lib/libruby.so.3.0(0x7fdad7255049) [0x7fdad7255049]
/usr/lib/libruby.so.3.0(0x7fdad724a7ce) [0x7fdad724a7ce]
/usr/lib/libruby.so.3.0(0x7fdad724d3de) [0x7fdad724d3de]
/usr/lib/libruby.so.3.0(rb_vm_exec+0x1e2) [0x7fdad72523c2]
/usr/lib/libruby.so.3.0(rb_funcallv+0x19c) [0x7fdad725a74c]
/usr/lib/libruby.so.3.0(0x7fdad71d5424) [0x7fdad71d5424]
/usr/lib/libruby.so.3.0(0x7fdad71d640c) [0x7fdad71d640c]
/usr/lib/libruby.so.3.0(ruby_process_options+0x10e) [0x7fdad71d716e]
/usr/lib/libruby.so.3.0(ruby_options+0x103) [0x7fdad70bf563]
ruby(0x55e824037078) [0x55e824037078]
/usr/lib/libc.so.6(__libc_start_main+0xd5) [0x7fdad6e6bb25]
ruby(_start+0x2e) [0x55e8240370ce]

Wrong extension method invoked?

The semantics of extension methods / modules change, when there is an additional intermediate rule being applied. This seems to be wrong. Here are two grammars demonstrating the assumed problem, plus test case.

test_spec.rb:
require 'rubygems'
gem 'citrus', "1.7.0"
require 'citrus'

 require 'spec'

 Citrus.load(File.dirname(__FILE__) + "/test.citrus")
 Citrus.load(File.dirname(__FILE__) + "/indirect.citrus")

 describe "extension methods" do
   it "a rule evaluates" do
     TestGrammar.parse("a").val.should == "a"
   end

   it "b rule evaluates" do
     TestGrammar.parse("b").val.should == "b"
   end

   it "any rule evalutes" do
     TestGrammar.parse("x").val.should == "any"
   end
 end

 describe "extension methods with another rule" do
   it "a rule evalutes" do
     IndirectGrammar.parse("a").val.should == "a" # fails, returns "all"
   end

   it "a rule evalutes" do
     IndirectGrammar.parse("b").val.should == "b" # fails, dto
   end

   it "a rule evalutes" do
     IndirectGrammar.parse("x").val.should == "any" # fails, dto
   end
 end

test.citrus:
grammar TestGrammar
rule any
a | b | [a-z] {def val ; "any" ; end}
end

  rule a
    "a" {def val ; "a" ; end}
  end

  rule b
    "b" {def val ; "b" ; end}
  end
end

indirect.citrus:
grammar IndirectGrammar
rule all
any {def val ; "all" ; end}
end

  rule any
    a | b | [a-z] {def val ; "any" ; end}
  end

  rule a
    "a" {def val ; "a" ; end}
  end

  rule b
    "b" {def val ; "b" ; end}
  end
end

I think indirect.citrus show the wrong behaviour. My understanding is that the extension method of the lowest rule should win. Am I wrong?

Block-scope: how to get Match instance of a selected rule

I'm having a hard time with this issue... I need to make a rule, that groups severall other rules, so to choose one from the group. The selected rule of the group should then be called by it's #value method.

It seems pretty easy, but somehow I can't figure out how to call the #value method on the group-selected-rule... my difficulty is in getting a handle to the group-selected-rule...

require 'citrus'

Citrus.eval(<<CITRUS)
grammar G
  rule all_the_candidates
    (cand1|cand2) { 
        matched_candidate = ....  # I want to call .value on the selected rule ("cand1" or "cand2" Match instance)
        matched_candidate.value   # How do I get the selected rule (in this case, cand1 or cand2)??   
      }
  end

  rule cand1
    '111' { "I'm 111" }
  end

  rule cand2
    '222' { "I'm 222" }
  end
end
CITRUS

match = G.parse('222')
puts match.value

confused by tagged repetitions

I'm confused by the combo of tags and repetition operators! For example, this grammar:

grammar G
rule foo
    ('A' opt:bar? 'Z') <Foo>
end

rule bar
    ('B')  <Bar>
end
end

Matching against the string "AZ", I'd expect match to have methods from module Foo, which it does.

Matching against the string "ABZ", I'd expect match to have methods from module Foo (yep), and 'match.opt' to have methods from module Bar, but opt hasn't been extended by Bar.

If I change the middle clause of rule foo to instead read (opt:bar)? (ie, add parentheses) then it works as expected.

So, I suspect precedence, but I'm at a loss to explain why that should determine whether module Bar is included or not. Any pointers?

(citrus 2.3.1 ruby 1.9.2)

Support for inheritance?

Hi,

I'd like to write an abstract grammar with two concrete instantiations and I expected Citrus to mimic ruby inheritance for this (I guess that Treetop does, btw). Unfortunately, the following example does not work:

grammar WithPar
  rule function
    /[a-z]/+ parens
  end
  rule parens '()' end
end

grammar WithBrack
  include WithPar
  root function
  rule parens '{}' end
end

I would expect the following to work, but it's not the case (see https://gist.github.com/1606092)

WithPar.parse "hello()"
WithBrack.parse "hello{}"

Is there a way to do this in Citrus?

single item in rule body results in infinite loop.

The following code runs into an infinite loop when value is called.

grammar X
  rule item
      num {
        num.value 
      }
  end

  rule num 
     /[0-9]+/ {to_i * 1000}
  end
end
x = X.parse("100")
x.value 

It is likely that item.value and num.value are somehow conflated and that it calls itself.

The following does not have a problem.

grammar X
  rule item
      (n1:num ',' n2:num) {
        n1.value + n2.value
      }
  end

  rule num 
     /[0-9]+/ {to_i}
  end
end

x=X.parse("100,200")
x.value # no problem.

Rule name beginning with 'super'

Probably easier for you to whip up a test case than me, but it seems that Citrus 2.4.0 doesn't like rule names that start with 'super'.

Does not run on Rubinius

Hi,

For some reason Citrus does not seem to run on Rubinius. The weird part is that it complaints about Malformed Citrus syntax. You can for example try the "Addition" example.

It's probably Rubinius's "fault", but maybe you can help them figure out the problem.

Thanks!

Specifying extension has odd precedence

After digging around for a while, I discovered that this is actually documented, but I don't see why it would be implemented this way; the following rule:

rule a
  b c d { ruby_code}
end
rule d
  'd'
end

attaches the extension to the instance of d used in the a rule, rather than to the a rule as I would have expected.

Not exactly an issue but a question

I am writing a translator which requires I be able to walk an abstract syntax tree based on a PEG. Can Citrus help create such an AST for the application to walk? If so, where in the documentation can I find that information? Thanks in advance.

Addition example from website doesn't work with 2.2.0

Saw talk at RubyConf, decided to play around with Citrus a wee bit on the flight home to PDX.

Tried following along with: http://mjijackson.com/citrus/example.html

Pasted second code sample (the one that adds two semantic blocks) to file, saved it. Launched IRB, required rubygems, required citrus, loaded addition file, parsed expression... then tried m.value and got:

Citrus::NoMatchError: No match named "term" in 1 + 2 + 3 (additive)
    from /Users/sam/rails/pdxrails/deps/lib/ruby/gems/1.8/gems/citrus-2.2.0/lib/citrus.rb:1198:in `method_missing'
    from (eval):3:in `value'
    from (irb):5

2.2.0 seems to have broken value, at least for root match?

Did something very bad happen in 2.2.0, or am I misunderstanding (as usual!):

grammar G

rule root
zero { 0 }
end

rule zero
'zero'
end

end

in 2.1.2 parse('zero').value #=> 0
in 2.2.0 parse('zero').value #=> Citrus::NoMatchError: No match named "value" in zero (zero)

README instructions not working (grammars not included)

So... very very nice looking peg parser (with sweet dependencies)
I read all the docs and now trying the README
Gem installs (on several ruby versions) but Citrus.load "addition" does not work
Now i guess "addition" should be "calc" and i'm happy to send a pr for that.
But the real issue seems that the grammar files are not included in the gem.
That one goes back to the gemspec, which only includes _.rb under lib
So i guess 983e0e7 is responsible, moving examples/_citrus to grammars but not updating the spec
(there is btw somewhere a dead link to examples in the docs)

Can fix all that if you want and would then release a new version

Torsten

Support for implicit whitespace handling?

I couldn't find this mentioned in the documentation, and the only previous discussion on this that I've found was in issue #3, so asking here...

In my experience writing Citrus grammars is very productive, except for one thing: whitespace handling. Transcribing various standard _BNF grammars (SQL, SPARQL, etc) into Citrus form is presently more painful than it could be, given that every terminal needs an explicit space_ appended to it as these grammars all assume that the input has been tokenized.

To keep the production rule definitions sane, as well as to keep them consistent with those in the standard grammar being transcribed, this incentivizes workarounds like the following:

grammar Keywords
  rule space [ \t\n\r] end
  rule ALL      `ALL`      space* end
  rule AS       `AS`       space* end
  rule BY       `BY`       space* end
  rule DISTINCT `DISTINCT` space* end
  rule FROM     `FROM`     space* end
  rule GROUP    `GROUP`    space* end
  rule HAVING   `HAVING`   space* end
  rule JOIN     `JOIN`     space* end
  rule SELECT   `SELECT`   space* end
  rule UNION    `UNION`    space* end
  rule WHERE    `WHERE`    space* end
  ...
end

grammar Tokens
  rule space [ \t\n\r] end
  rule digit                 [0-9] space* end
  rule double_quote          '"' space* end
  rule percent               '%' space* end
  rule ampersand             '&' space* end
  rule left_paren            '(' space* end
  rule right_paren           ')' space* end
  rule asterisk              '*' space* end
  rule plus_sign             '+' space* end
  ...
end

grammar MyGrammar
  include Keywords
  include Tokens

  rule query_specification
    SELECT set_quantifier? select_list table_expression
  end

  rule set_quantifier
    DISTINCT | ALL
  end

  ...
end

The above approach works fine, of course, but seems rather redundant and not a little laborious.

Is there by any chance a magic option I've missed somewhere that would automatically consume any trailing whitespace after recognizing a terminal? Alternatively, is there perhaps a way to feed the #parse method with a sequence of tokens (at its simplest, an Enumerable of strings) instead of giving it an input string?

Thanks for taking the time to read this, and kudos for the awesome job you've done on Citrus so far: the documentation is superb and the source code is a pleasure to read.

Question about repetition of nested rules

In trying to use the following rule

rule metadata_body
  timestamp server_id end_pos word (space | number | ":")* "\n"
end

to parse the line

"#111215 10:45:17 server id 6001  end_log_pos 363693   Write_rows: table id 3694656 flags: STMT_END_F\n"

the call to #parse hangs indefinitely, making it seem like I've caused an infinite loop somewhere.

Being new to Citrus and rather new to PEGs altogether, I'm sure there's some foolish newbie mistake at play here.

What I'm trying to express is "match any word, space, number, or colon, up to the newline character."

Can someone point me toward A Better Way™?

Very difficult to use "?" (zero or one) with values

Hi MIchael. I am sure I'm missing the "citrus (or PEG?) way" here, but I am having terrible trouble finding a good way of expressing optional rules' values in my grammars. Below is a (contrived) example of what I'm trying to do, and what I end up doing.

I'm certain there is a much better way to query/extract optional rule values, but I'm at a a loss.

grammar broken_but_clear_intention

```
rule grade
    (letter_grade modifier?) {
        letter_grade.value + (modifier ? modifier.value : 0)
    }
end

rule letter_grade
    'A' { 4.0 } |
    'B' { 3.0 } |
    'C' { 2.0 } |
    'D' { 1.0 }
end

rule modifier
    '+' { +0.5 } |
    '-' { -0.5 }
end
```

end

But this works:

grammar works_but_hacky

rule grade
    (letter_grade modifier) {
        letter_grade.value + modifier.value
    }
end

rule letter_grade
    'A' { 4.0 } |
    'B' { 3.0 } |
    'C' { 2.0 } |
    'D' { 1.0 }
end

# yech!
rule modifier
    modifier_symbol | (!modifier_symbol) {0} 
end

rule modifier_symbol
    '+' { +0.5 } |
    '-' { -0.5 }
end

end

I also tried assigning labels to the match, but that syntax isn't great either. Any suggestions?

Jruby 1.6.2 issue(s)

The test suite does not work on the current jruby 1.6.2 on OS X 10.7.0

The failure is:

Loaded suite file_test
Started
................FF.F................................................................................................................
Finished in 1.938000 seconds.

  1. Failure:
    test_character_class(CitrusFileTest) [file_test.rb:498]:
    </[]/> (US-ASCII) expected but was
    </[
    ]/> (US-ASCII).

  2. Failure:
    test_character_class_a_z(CitrusFileTest) [file_test.rb:504]:
    </[a-z]/> (US-ASCII) expected but was
    </[a-z]/> (US-ASCII).

  3. Failure:
    test_character_class_nested_square_brackets(CitrusFileTest) [file_test.rb:510]:
    </[[-]]/> (US-ASCII) expected but was
    </[[-]]/> (US-ASCII).

132 tests, 282 assertions, 3 failures, 0 errors, 0 skips

Citrus successfully parsing where it shouldn't be

hi,

With the following grammar:

grammar Cit
  rule number
    ([0-9]+ [\s]*)
  end
end

I can successfully parse the text 65 +, which I don't believe should happen. Treetop does not parse that text with an identical grammar (Literally, in this case. No changes are necessary to it).

thanks!

Broken extension on Ruby 2.5

using Ruby 2.5 on Windows. I am testing simple Addition grammar to get the value from extension. Here is the output

irb(main):001:0> require 'citrus'
=> true
irb(main):002:0> Citrus.load 'addition'
=> [Addition]
irb(main):003:0> m = Addition.parse '1 + 2 + 3'
=> "1 + 2 + 3"
irb(main):004:0> m.inspect
=> "\"1 + 2 + 3\""
irb(main):005:0> puts m.value
Traceback (most recent call last):
        3: from D:/ruby/ruby_packages_nobk/rubyinstaller-2.5.0-1-x64/bin/irb.cmd:19:in `<main>'
        2: from (irb):5
        1: from <main>:1:in `block in <main>'
NameError (undefined local variable or method `number' for "1 + 2 + 3":Citrus::Match)
irb(main):006:0>

I modified grammar a little and it can work but look not clean as before.

grammar Addition
  rule additive
    (number plus term:(additive | number)) {
       capture("number").value + capture("term").value
    }
  end

  rule number
    ([0-9]+ space) {
      to_s.to_i
    }
  end

  rule plus
    '+' space
  end

  rule space
    [ \t]*
  end
end

can you check why the previous not working?

Release 3.0.3?

Hi is it possible to release 3.0.3 on rubygems please? And/or create a 3.0.3 tag?

Ruby 2.2.0 Warnings for named arguments

citrus-3.0.1/lib/citrus.rb:202: warning: circular argument reference - pos
citrus-3.0.1/lib/citrus.rb:214: warning: circular argument reference - pos
citrus-3.0.1/lib/citrus.rb:226: warning: circular argument reference - pos
citrus-3.0.1/lib/citrus.rb:234: warning: circular argument reference - pos

pos = pos should be changed to pos = pos(), see https://bugs.ruby-lang.org/issues/10314

How to include one citrus grammar in another...

Is it possible to include one Citrus grammar in another, if the grammar exists in a separate file? I cannot seem to "require" the citrus file. If two grammars are in the same file, I can successfully include one in the other. But if I want to store one in a different file, is it possible to somehow require it?

multiple parses show "warning: already initialized constant Kget"

If a program makes multiple citrus parses, then a ruby warning will be shown:

"...gems/citrus-2.4.0/lib/citrus/file.rb:56: warning: already initialized constant Kget"

This should be easy to fix (if constant is already defined, don't redefine it), but when I dived in the source code of file.rb, I've seen in line 56 that the constant definition uses metaprogramming, so I better leave the change for expert hands...

PD: The parser is fantastic :)
PD2: I've tested and seen that citrus is 10x faster in MRI-ruby than in Jruby - maybe it would be usefull to leave some tip in the readme about it

Trouble flattening lists

Hi Michael,

Any idea how to do list flattening in a straightforward way with nested rules? If I use the construct below with the input "foo1 foo2 foo3" and then call the value method on my tree, I get "Stack level too deep". Seems that Things will call the morethings method on itself instead of on the matched substring. This appears to be a bug, but maybe I've missed something here...

I can make it work if I add a second rule so that the two rules alternate, but it sure does make the grammar file ugly!

Many thanks,
Dan

grammar SampleGrammar
rule ROOT
(things:Things WS* EOF)
{
def value
list = []
list += things.value
return list
end
}
end

rule Things
    (thing:THING WS* (morethings:Things)?)
    {
        def value
            list = []
            list << thing.value
            list += morethings.value unless morethings.nil?
            return list
        end
    }
end

rule THING
    [a-zA-Z0-9]+
end

rule EOF
    !.
end

rule WS
    [\s\t\r\n\f] 
end

end

How to obtain repeated sequences in the API?

I am attempting to parse CSS file and want to obtain all the property values.

Below is some code where I was starting to do a sort of CSS file parsing and want to obtain all of the properties (foo: bar; cat: dog, hello: world).

I looked through the docs and code and wasn't sure how best to accomplish this. I see in your examples where you have digit { to_int } but in my case I don't want all the other junk ";" space and I want things as an array of properties. I'm sure you have a good way of doing it in the framework so I wanted to learn how to do so.

I saw the to_a method but it seemed to retrieve everything and I would have to get rid of the semicolons and spaces that were already parsed.

See the #HELP comment below to see where I am trying to retrieve an array of the properties

I would appreciate any insight you can give me since I am really new to this type of parsing.

Thanks in advance!

Jeff

Citrus.eval(<<CITRUS)
grammar Csexample

rule line
(selector space block) {
[selector.value, block.value]
}
end

rule selector
words
end

rule block
("{" space propvalue morepropvalue* space "}") {

  # HELP how do I retrieve an arry of all the property values (propvalue + morepropvalue*)

}

end

rule morepropvalue
((";" space propvalue)) {
propvalue.value
}
end

rule propvalue
(space prop ":" space val) {
{ prop.value.to_sym => val.value }
}
end

rule val
(words | inner_block)+
end

rule prop
words
end

rule space
[ \t]*
end

rule inner_block
"{" words "}"
end

rule words
[#a-zA-Z0-9_.-() ]+
end

end
CITRUS

m = Csexample.parse "#foo .hi { bar: baz; cat: dog; hot: flash }"
puts m.value.inspect

Unable to collect parse results

Sorry for asking here, but nobody approved my question in citrus–users google group, so i'll try ask here (I really need the answer).

So, I have some rules to parse something like this:

header1
content1

header2
content2

header3
content3

And then I need to get an array of hashes:

[{header: 'header1', content: 'content1'}, {header: 'header2', content: 'content2'}, {header: 'header3', content: 'content3'}]

So, I think I wrote right rules, something like this:

grammar Markdown
  rule section_list
    section (ln section_list)?
  end

  rule section
    header ln content
  end

  rule header
    'header'
  end

  rule content
    'content'
  end

  rule ln
    "\n"+
  end
end

When I parse it and run #dump method, it output is:

"header\ncontent\n\nheader\ncontent\n\nheader\ncontent" rule=section (ln section_list)?, offset=0, length=46
 "header\ncontent" rule=section, offset=0, length=14
  "header" rule=header, offset=0, length=6
  "\n" rule=ln, offset=6, length=1
   "\n" rule="\n", offset=6, length=1
  "content" rule=content, offset=7, length=7
 "\n\nheader\ncontent\n\nheader\ncontent" rule=(ln section_list)?, offset=14, length=32
  "\n\nheader\ncontent\n\nheader\ncontent" rule=ln section_list, offset=14, length=32
   "\n\n" rule=ln, offset=14, length=2
    "\n" rule="\n", offset=14, length=1
    "\n" rule="\n", offset=15, length=1
   "header\ncontent\n\nheader\ncontent" rule=section_list, offset=16, length=30
    "header\ncontent" rule=section, offset=16, length=14
     "header" rule=header, offset=16, length=6
     "\n" rule=ln, offset=22, length=1
      "\n" rule="\n", offset=22, length=1
     "content" rule=content, offset=23, length=7
    "\n\nheader\ncontent" rule=(ln section_list)?, offset=30, length=16
     "\n\nheader\ncontent" rule=ln section_list, offset=30, length=16
      "\n\n" rule=ln, offset=30, length=2
       "\n" rule="\n", offset=30, length=1
       "\n" rule="\n", offset=31, length=1
      "header\ncontent" rule=section_list, offset=32, length=14
       "header\ncontent" rule=section, offset=32, length=14
        "header" rule=header, offset=32, length=6
        "\n" rule=ln, offset=38, length=1
         "\n" rule="\n", offset=38, length=1
        "content" rule=content, offset=39, length=7
       "" rule=(ln section_list)?, offset=46, length=0

But if I output #captures method, I see something like this:

{
  0=>"header\ncontent\n\nheader\ncontent\n\nheader\ncontent", 
  :section=>["header\ncontent"], 
  1=>"header\ncontent", 
  :ln=>["\n\n"], 
  :section_list=>["header\ncontent\n\nheader\ncontent"], 
  2=>"\n\nheader\ncontent\n\nheader\ncontent"
}

So, the main question is: how collect my parse results into a hash?

Thanks a lot for your awesome lib, help me please.

A way to pass context to grammar/rules

I want to parse and compute a formula using Citrus, but there is a problem - exact values contained in a different object. Obvious way to deal with it is to pass a context to parser, but I do not see a way to do that in current version.

How could I achieve my goal?
Thanks.

Review for releasing 2.5.0

@mjijackson Could you have a quick check at the following:

v2.4.1...2.5.x

I've created a 2.5.x branch with non-breaking changes since 2.4.1 and added a few warnings about breaking changes to appear in 3.0. I think having a 2.5.0 release might be smoother for users (including me) than going from 2.4.1 to 3.0 directly.

Except maybe for "Don't hide Errno::ENOENT behind Citrus::Error", I'm rather confident that 2.5.0 is backward compatible with 2.4.1. WDYT? Is it ok for you to release 2.5.0 as such?

Help creating a rule for a last name

Hi,

I'm trying to create a rule for a last name. This is what I have come up with:

rule last_name
  [A-Za-z]+ space [iI]+ | [iI]+ &[^iI]+ | [^iI] [A-Za-z]+
end

So:

  • Match a "name", followed by a space, followed by any number of i's. Or
  • If the first characters are one or more i's, then something that is not an i must follow. Or
  • If the first character is not an i, then any "name" can follow

If correct, this should be able to parse:

  • Rule 1) Love III
  • Rule 2) Immelman
  • Rule 3) Donald

But it fails on Immelman. It would also fail on for example Love IIIx.

I guess my second rule is wrong? But why?

Can't access value of submatch in certain grammars

Probably operator error, but I'm having trouble with something akin to below. I'm trying to write a rule which transforms a submatch's value in some way. In this contrived example, I'm multiplying the submatch value by 1000 for illustrative purposes.

# Good.parse('2').value # => 2000
grammar Good
    rule root
        (space r:(one | two)) { r.value*1000 }
    end

    rule one
        "1" { 1 }
    end

    rule two
        "2" { 2 }
    end

     rule space
        [ ]*
     end
end

# Bad.parse('2').value # => "no such match r"
grammar Bad
    rule root
        # remove the dummy space token...
        (r:(one | two)) { r.value*1000 }
    end

    rule one
        "1" { 1 }
    end

    rule two
        "2" { 2 }
    end
end

# Bad2.parse('2').value # => ruby dies (1.9.2p0 on Windows)
grammar Bad2
    rule root
        # shouldn't really need the dummy label?
        (one | two) { value*1000 }
    end

    rule one
        "1" { 1 }
    end

    rule two
        "2" { 2 }
    end
end

Unexpected behavior for names method

grammar Alias
  rule something
    digit
  end

  rule digit
    [0-9]
  end
end

require 'rubygems'
require 'citrus'
Citrus.load('alias')
m = Alias.parse('1')

puts m.names
# => [:digit]
# I would expect...
# => [:something, :digit]

tags not working in single-clause rules

#!/usr/bin/env ruby
require 'citrus'

Citrus.eval(<<CITRUS)
grammar G
    rule foo
        bar:'BAR'
    end

    rule foo2
        bar:'BAR' 'BAR'
    end
end
CITRUS

match = G.parse('BAR')
puts "match: #{match}" # 'BAR', ok
puts "match.captures: #{match.captures}" # empty {}!
puts "match.matches: #{match.matches}" # empty []!
puts "match.bar: #{match.bar}" # nil - eh?

puts "----"

match = G.parse('BARBAR', :root => :foo2)
puts "match: #{match}" # 'BARBAR', ok
puts "match.captures: #{match.captures}" # bar=>["BAR"] ok
puts "match.matches: #{match.matches}" # ["BAR", "BAR"] ok
puts "match.bar: #{match.bar}" # BAR, ok

in rule 'foo', I can't use 'bar' as an accessor, and captures is empty. If I add another clause as in rule 'foo2' it starts working again.

(citrus 2.3.1 ruby 1.9.2)

Cirtus value blocks can't accept string expansion

For me,

rule every_n_days
    ('every ' number' days') {
        "FREQ=DAILY; INTERVAL=#{number.value}"  # => fails to laod
        "FREQ=DAILY; INTERVAL=" + number.value.to_s  # => works!
     }
end

Can't be loaded because the the #{quantity.value} in the string expression. Alas, I wonder if this isn't a pretty common idiom?

Bad predicate behavior?

From the readme documentation:

&'a' 'b'      # match a "b" preceded by an "a"

I haven't tried the library yet but got stuck on that line - does citrus implement and-predicates so that they consume characters? In normal PEG that expression would always fail because they would be looking at the same spot in the stream.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.