citrus 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
- = Background
1
+ # Background
2
+
2
3
 
3
4
  In order to be able to use Citrus effectively, you must first understand the
4
5
  difference between syntax and semantics. Syntax is a set of rules that govern
@@ -9,8 +10,8 @@ sentences should end with a period.
9
10
  Semantics are the rules by which meaning may be derived in a language. For
10
11
  example, as you read a book you are able to make some sense of the particular
11
12
  way in which words on a page are combined to form thoughts and express ideas
12
- because you understand what the words themselves mean and you can understand
13
- what they mean collectively.
13
+ because you understand what the words themselves mean and you understand what
14
+ they mean collectively.
14
15
 
15
16
  Computers use a similar process when interpreting code. First, the code must be
16
17
  parsed into recognizable symbols or tokens. These tokens may then be passed to
@@ -22,14 +23,15 @@ powerful parsers that are simple to understand and easy to create and maintain.
22
23
 
23
24
  In Citrus, there are three main types of objects: rules, grammars, and matches.
24
25
 
25
- == Rules
26
+ ## Rules
26
27
 
27
- A Rule[link:api/classes/Citrus/Rule.html] is an object that specifies some matching behavior on a string. There are
28
- two types of rules: terminals and non-terminals. Terminals can be either Ruby
29
- strings or regular expressions that specify some input to match. For example, a
30
- terminal created from the string "end" would match any sequence of the
31
- characters "e", "n", and "d", in that order. A terminal created from a regular
32
- expression uses Ruby's regular expression engine to attempt to create a match.
28
+ A [Rule](api/classes/Citrus/Rule.html) is an object that specifies some matching
29
+ behavior on a string. There are two types of rules: terminals and non-terminals.
30
+ Terminals can be either Ruby strings or regular expressions that specify some
31
+ input to match. For example, a terminal created from the string "end" would
32
+ match any sequence of the characters "e", "n", and "d", in that order. A
33
+ terminal created from a regular expression uses Ruby's regular expression engine
34
+ to attempt to create a match.
33
35
 
34
36
  Non-terminals are rules that may contain other rules but do not themselves match
35
37
  directly on the input. For example, a Repeat is a non-terminal that may contain
@@ -37,34 +39,35 @@ one other rule that will try and match a certain number of times. Several other
37
39
  types of non-terminals are available that will be discussed later.
38
40
 
39
41
  Rule objects may also have semantic information associated with them in the form
40
- of Ruby modules. These modules contain methods that will be used to extend any
41
- match objects created by the rule with which they are associated.
42
+ of Ruby modules. Rules use these modules to extend the matches they create.
42
43
 
43
- == Grammars
44
+ ## Grammars
44
45
 
45
- A Grammar[link:api/classes/Citrus/Grammar.html] is a container for rules. Usually the rules in a grammar collectively
46
+ A grammar is a container for rules. Usually the rules in a grammar collectively
46
47
  form a complete specification for some language, or a well-defined subset
47
48
  thereof.
48
49
 
49
- A Citrus grammar is really just a souped-up Ruby module. These modules may be
50
+ A Citrus grammar is really just a souped-up Ruby
51
+ [module](http://ruby-doc.org/core/classes/Module.html). These modules may be
50
52
  included in other grammar modules in the same way that Ruby modules are normally
51
- used. This property allows you to divide a complex grammar into reusable pieces
52
- that may be combined dynamically at runtime. Any grammar rule with the same name
53
- as a rule in an included grammar may access that rule with a mechanism similar
54
- to Ruby's super keyword.
55
-
56
- == Matches
57
-
58
- Matches are created by rule objects when they match on the input. A Match[link:api/classes/Citrus/Match.html]
59
- contains the string of text that made up the match as well as its offset in the
60
- original input string. During a parse, matches are arranged in a tree structure
61
- where any match may contain any number of other matches. This structure is
62
- determined by the way in which the rule that generated each match is used in the
63
- grammar.
64
-
65
- For example, a match that is created from a non-terminal rule that contains
66
- several other terminals will likewise contain several matches, one for each
67
- terminal.
53
+ used. This property allows you to divide a complex grammar into more manageable,
54
+ reusable pieces that may be combined at runtime. Any grammar rule with the same
55
+ name as a rule in an included grammar may access that rule with a mechanism
56
+ similar to Ruby's super keyword.
57
+
58
+ ## Matches
59
+
60
+ Matches are created by rule objects when they match on the input. A
61
+ [Match](api/classes/Citrus/Match.html) in Citrus is actually a
62
+ [String](http://ruby-doc.org/core/classes/String.html) with some extra
63
+ information attached such as the name(s) of the rule(s) which generated the
64
+ match as well as its offset in the original input string.
65
+
66
+ During a parse, matches are arranged in a tree structure where any match may
67
+ contain any number of other matches. This structure is determined by the way in
68
+ which the rule that generated each match is used in the grammar. For example, a
69
+ match that is created from a non-terminal rule that contains several other
70
+ terminals will likewise contain several matches, one for each terminal.
68
71
 
69
72
  Match objects may be extended with semantic information in the form of methods.
70
73
  These methods can interpret the text of a match using the wealth of information
@@ -0,0 +1,145 @@
1
+ # Example
2
+
3
+
4
+ Below is an example of a simple grammar that is able to parse strings of
5
+ integers separated by any amount of white space and a `+` symbol.
6
+
7
+ grammar Addition
8
+ rule additive
9
+ number plus (additive | number)
10
+ end
11
+
12
+ rule number
13
+ [0-9]+ space
14
+ end
15
+
16
+ rule plus
17
+ '+' space
18
+ end
19
+
20
+ rule space
21
+ [ \t]*
22
+ end
23
+ end
24
+
25
+ Several things to note about the above example:
26
+
27
+ * Grammar and rule declarations end with the `end` keyword
28
+ * A Sequence of rules is created by separating expressions with a space
29
+ * Likewise, ordered choice is represented with a vertical bar
30
+ * Parentheses may be used to override the natural binding order
31
+ * Rules may refer to other rules in their own definitions simply by using the
32
+ other rule's name
33
+ * Any expression may be followed by a quantifier
34
+
35
+ ## Interpretation
36
+
37
+ The grammar above is able to parse simple mathematical expressions such as "1+2"
38
+ and "1 + 2+3", but it does not have enough semantic information to be able to
39
+ actually interpret these expressions.
40
+
41
+ At this point, when the grammar parses a string it generates a tree of
42
+ [Match](api/classes/Citrus/Match.html) objects. Each match is created by a rule.
43
+ A match knows what text it contains, its offset in the original input, and what
44
+ submatches it contains.
45
+
46
+ Submatches are created whenever a rule contains another rule. For example, in
47
+ the grammar above the number rule matches a string of digits followed by white
48
+ space. Thus, a match generated by the number rule will contain two submatches.
49
+
50
+ We can use Ruby's block syntax to create a module that will be attached to these
51
+ matches when they are created and is used to lazily extend them when we want to
52
+ interpret them. The following example shows one way to do this.
53
+
54
+ grammar Addition
55
+ rule additive
56
+ (number plus term:(additive | number)) {
57
+ def value
58
+ number.value + term.value
59
+ end
60
+ }
61
+ end
62
+
63
+ rule number
64
+ ([0-9]+ space) {
65
+ def value
66
+ strip.to_i
67
+ end
68
+ }
69
+ end
70
+
71
+ rule plus
72
+ '+' space
73
+ end
74
+
75
+ rule space
76
+ [ \t]*
77
+ end
78
+ end
79
+
80
+ In this version of the grammar we have added two semantic blocks, one each for
81
+ the additive and number rules. These blocks contain methods that will be present
82
+ on all match objects that result from matches of those particular rules. It's
83
+ easiest to explain what is going on here by starting with the lowest level
84
+ block, which is defined within the number rule.
85
+
86
+ The semantic block associated with the number rule defines one method, value.
87
+ Inside this method, we can see that the value of a number match is determined to
88
+ be its text value, stripped of white space and converted to an integer. Remember
89
+ that matches are simply strings, so the `strip` method in this case is actually
90
+ `String#strip`.
91
+
92
+ The `additive` rule also extends its matches with a value method. Notice the use
93
+ of the `term` label within the rule definition. This label allows the match that
94
+ is created by either the additive or the number rule to be retrieved using the
95
+ `term` label. The value of an additive is determined to be the values of its
96
+ `number` and `term` matches added together using Ruby's addition operator.
97
+
98
+ Since additive is the first rule defined in the grammar, any match that results
99
+ from parsing a string with this grammar will have a `value` method that can be
100
+ used to recursively calculate the collective value of the entire match tree.
101
+
102
+ To give it a try, save the code for the Addition grammar in a file called
103
+ addition.citrus. Next, assuming you have the Citrus gem installed, try the
104
+ following sequence of commands in a terminal.
105
+
106
+ $ irb
107
+ > require 'citrus'
108
+ => true
109
+ > Citrus.load 'addition'
110
+ => [Addition]
111
+ > m = Addition.parse '1 + 2 + 3'
112
+ => #<Citrus::Match ...
113
+ > m.value
114
+ => 6
115
+
116
+ Congratulations! You just ran your first piece of Citrus code.
117
+
118
+ Take a look at
119
+ [examples/calc.citrus](http://github.com/mjijackson/citrus/blob/master/examples/calc.citrus)
120
+ for an example of a calculator that is able to parse and evaluate more complex
121
+ mathematical expressions.
122
+
123
+ ## Implicit Value
124
+
125
+ It is very common for a grammar to only have one interpretation for a given
126
+ symbol. For this reason, you may find yourself writing a `value` method for
127
+ every rule in your grammar. Because this can be tedious, Citrus allows you to
128
+ omit defining such a method if you choose. For example, the `additive` and
129
+ `number` rules from the simple calculator example above could also be written
130
+ as:
131
+
132
+ rule additive
133
+ (number plus term:(additive | number)) {
134
+ number.value + term.value
135
+ }
136
+ end
137
+
138
+ rule number
139
+ ([0-9]+ space) {
140
+ strip.to_i
141
+ }
142
+ end
143
+
144
+ Since no method name is explicitly specified in the semantic blocks, they may be
145
+ called using the `value` method.
@@ -0,0 +1,18 @@
1
+ Citrus is a compact and powerful parsing library for
2
+ [Ruby](http://ruby-lang.org/) that combines the elegance and expressiveness of
3
+ the language with the simplicity and power of
4
+ [parsing expressions](http://en.wikipedia.org/wiki/Parsing_expression_grammar).
5
+
6
+
7
+ # Installation
8
+
9
+
10
+ Via [RubyGems](http://rubygems.org/):
11
+
12
+ $ sudo gem install citrus
13
+
14
+ From a local copy:
15
+
16
+ $ git clone git://github.com/mjijackson/citrus.git
17
+ $ cd citrus
18
+ $ rake package && sudo rake install
@@ -1,4 +1,5 @@
1
- = License
1
+ # License
2
+
2
3
 
3
4
  Copyright 2010 Michael Jackson
4
5
 
@@ -0,0 +1,13 @@
1
+ # Links
2
+
3
+
4
+ The primary resource for all things to do with parsing expressions can be found
5
+ on the original [Packrat and Parsing Expression Grammars page](http://pdos.csail.mit.edu/~baford/packrat) at MIT.
6
+
7
+ Also, a useful summary of parsing expression grammars can be found on
8
+ [Wikipedia](http://en.wikipedia.org/wiki/Parsing_expression_grammar).
9
+
10
+ Citrus draws inspiration from another Ruby library for writing parsing
11
+ expression grammars, Treetop. While Citrus' syntax is similar to that of
12
+ [Treetop](http://treetop.rubyforge.org), it's not identical. The link is
13
+ included here for those who may wish toexplore an alternative implementation.
@@ -0,0 +1,129 @@
1
+ # Syntax
2
+
3
+
4
+ The most straightforward way to compose a Citrus grammar is to use Citrus' own
5
+ custom grammar syntax. This syntax borrows heavily from Ruby, so it should
6
+ already be familiar to Ruby programmers.
7
+
8
+ ## Terminals
9
+
10
+ Terminals may be represented by a string or a regular expression. Both follow
11
+ the same rules as Ruby string and regular expression literals.
12
+
13
+ 'abc'
14
+ "abc\n"
15
+ /\xFF/
16
+
17
+ Character classes and the dot (match anything) symbol are supported as well for
18
+ compatibility with other parsing expression implementations.
19
+
20
+ [a-z0-9] # match any lowercase letter or digit
21
+ [\x00-\xFF] # match any octet
22
+ . # match anything, even new lines
23
+
24
+ See [FixedWidth](api/classes/Citrus/FixedWidth.html) and
25
+ [Expression](api/classes/Citrus/Expression.html) for more information.
26
+
27
+ ## Repetition
28
+
29
+ Quantifiers may be used after any expression to specify a number of times it
30
+ must match. The universal form of a quantifier is N*M where N is the minimum and
31
+ M is the maximum number of times the expression may match.
32
+
33
+ 'abc'1*2 # match "abc" a minimum of one, maximum
34
+ # of two times
35
+ 'abc'1* # match "abc" at least once
36
+ 'abc'*2 # match "abc" a maximum of twice
37
+
38
+ The + and ? operators are supported as well for the common cases of 1* and *1
39
+ respectively.
40
+
41
+ 'abc'+ # match "abc" at least once
42
+ 'abc'? # match "abc" a maximum of once
43
+
44
+ See [Repeat](api/classes/Citrus/Repeat.html) for more information.
45
+
46
+ ## Lookahead
47
+
48
+ Both positive and negative lookahead are supported in Citrus. Use the & and !
49
+ operators to indicate that an expression either should or should not match. In
50
+ neither case is any input consumed.
51
+
52
+ &'a' 'b' # match a "b" preceded by an "a"
53
+ !'a' 'b' # match a "b" that is not preceded by an "a"
54
+ !'a' . # match any character except for "a"
55
+
56
+ A special form of lookahead is also supported which will match any character
57
+ that does not match a given expression.
58
+
59
+ ~'a' # match all characters until an "a"
60
+ ~/xyz/ # match all characters until /xyz/ matches
61
+
62
+ See [AndPredicate](api/classes/Citrus/AndPredicate.html),
63
+ [NotPredicate](api/classes/Citrus/NotPredicate.html), and
64
+ [ButPredicate](api/classes/Citrus/ButPredicate.html) for more information.
65
+
66
+ ## Sequences
67
+
68
+ Sequences of expressions may be separated by a space to indicate that the rules
69
+ should match in that order.
70
+
71
+ 'a' 'b' 'c' # match "a", then "b", then "c"
72
+ 'a' [0-9] # match "a", then a numeric digit
73
+
74
+ See [Sequence](api/classes/Citrus/Sequence.html) for more information.
75
+
76
+ ## Choices
77
+
78
+ Ordered choice is indicated by a vertical bar that separates two expressions.
79
+ Note that any operator binds more tightly than the bar.
80
+
81
+ 'a' | 'b' # match "a" or "b"
82
+ 'a' 'b' | 'c' # match "a" then "b" (in sequence), or "c"
83
+
84
+ See [Choice](api/classes/Citrus/Choice.html) for more information.
85
+
86
+ ## Super
87
+
88
+ When including a grammar inside another, all rules in the child that have the
89
+ same name as a rule in the parent also have access to the "super" keyword to
90
+ invoke the parent rule.
91
+
92
+ See [Super](api/classes/Citrus/Super.html) for more information.
93
+
94
+ ## Labels
95
+
96
+ Match objects may be referred to by a different name than the rule that
97
+ originally generated them. Labels are created by placing the label and a colon
98
+ immediately preceding any expression.
99
+
100
+ chars:/[a-z]+/ # the characters matched by the regular
101
+ # expression may be referred to as "chars"
102
+ # in a block method
103
+
104
+ See [Label](api/classes/Citrus/Label.html) for more information.
105
+
106
+ ## Precedence
107
+
108
+ The following table contains a list of all Citrus operators and their
109
+ precedence. A higher precedence indicates tighter binding.
110
+
111
+ | Operator | Name | Precedence
112
+ | ------------ | ------------------------- | ----------
113
+ | '' | String (single quoted) | 6
114
+ | "" | String (double quoted) | 6
115
+ | [] | Character class | 6
116
+ | . | Dot (any character) | 6
117
+ | // | Regular expression | 6
118
+ | () | Grouping | 6
119
+ | * | Repetition (arbitrary) | 5
120
+ | + | Repetition (one or more) | 5
121
+ | ? | Repetition (zero or one) | 5
122
+ | & | And predicate | 4
123
+ | ! | Not predicate | 4
124
+ | ~ | But predicate | 4
125
+ | : | Label | 4
126
+ | <> | Extension (module name) | 3
127
+ | {} | Extension (literal) | 3
128
+ | e1 e2 | Sequence | 2
129
+ | e1 &#124; e2 | Ordered choice | 1
@@ -1,94 +1,100 @@
1
- # A grammar for mathematical formulas that apply the basic four operations to
2
- # non-negative numbers (integers and floats), respecting operator precedence and
3
- # ignoring whitespace.
1
+ # A grammar for mathematical formulas that apply basic mathematical operations
2
+ # to all numbers, respecting operator precedence and grouping of expressions
3
+ # while ignoring whitespace.
4
4
  #
5
5
  # An identical grammar that is written using pure Ruby can be found in calc.rb.
6
6
  grammar Calc
7
+
8
+ ## Hierarchy
9
+
7
10
  rule term
8
11
  additive | factor
9
12
  end
10
13
 
11
14
  rule additive
12
- (factor operator:(plus | minus) term) {
13
- def value
14
- operator.apply(factor, term)
15
- end
15
+ (factor additive_operator term) {
16
+ additive_operator.value(factor.value, term.value)
16
17
  }
17
18
  end
18
19
 
19
20
  rule factor
20
- multiplicative | primary
21
+ multiplicative | prefix
21
22
  end
22
23
 
23
24
  rule multiplicative
24
- (primary operator:(star | slash) factor) {
25
- def value
26
- operator.apply(primary, factor)
27
- end
25
+ (prefix multiplicative_operator factor) {
26
+ multiplicative_operator.value(prefix.value, factor.value)
28
27
  }
29
28
  end
30
29
 
31
- rule primary
32
- term_paren | number
30
+ rule prefix
31
+ prefixed | exponent
33
32
  end
34
33
 
35
- rule term_paren
36
- (lparen term rparen) {
37
- def value
38
- term.value
39
- end
34
+ rule prefixed
35
+ (unary_operator prefix) {
36
+ unary_operator.value(prefix.value)
40
37
  }
41
38
  end
42
39
 
40
+ rule exponent
41
+ exponential | primary
42
+ end
43
+
44
+ rule exponential
45
+ (primary exponential_operator prefix) {
46
+ exponential_operator.value(primary.value, prefix.value)
47
+ }
48
+ end
49
+
50
+ rule primary
51
+ group | number
52
+ end
53
+
54
+ rule group
55
+ (lparen term rparen) { term.value }
56
+ end
57
+
58
+ ## Syntax
59
+
43
60
  rule number
44
61
  float | integer
45
62
  end
46
63
 
47
64
  rule float
48
- ([0-9]+ '.' [0-9]+ space) {
49
- def value
50
- text.strip.to_f
51
- end
52
- }
65
+ (digits '.' digits space) { strip.to_f }
53
66
  end
54
67
 
55
68
  rule integer
56
- ([0-9]+ space) {
57
- def value
58
- text.strip.to_i
59
- end
60
- }
69
+ (digits space) { strip.to_i }
70
+ end
71
+
72
+ rule digits
73
+ [0-9]+ ('_' [0-9]+)*
61
74
  end
62
75
 
63
- rule plus
64
- ('+' space) {
65
- def apply(factor, term)
66
- factor.value + term.value
67
- end
76
+ rule additive_operator
77
+ (('+' | '-') space) { |a, b|
78
+ a.send(strip, b)
68
79
  }
69
80
  end
70
81
 
71
- rule minus
72
- ('-' space) {
73
- def apply(factor, term)
74
- factor.value - term.value
75
- end
82
+ rule multiplicative_operator
83
+ (('*' | '/' | '%') space) { |a, b|
84
+ a.send(strip, b)
76
85
  }
77
86
  end
78
87
 
79
- rule star
80
- ('*' space) {
81
- def apply(primary, factor)
82
- primary.value * factor.value
83
- end
88
+ rule exponential_operator
89
+ ('**' space) { |a, b|
90
+ a ** b
84
91
  }
85
92
  end
86
93
 
87
- rule slash
88
- ('/' space) {
89
- def apply(primary, factor)
90
- primary.value / factor.value
91
- end
94
+ rule unary_operator
95
+ (('~' | '+' | '-') space) { |n|
96
+ # Unary + and - require an @.
97
+ n.send(strip == '~' ? strip : '%s@' % strip)
92
98
  }
93
99
  end
94
100