citrus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README ADDED
@@ -0,0 +1,86 @@
1
+
2
+
3
+ ~* Citrus *~
4
+
5
+ Parsing Expressions for Ruby
6
+
7
+
8
+ Citrus is a compact and powerful parsing library for Ruby that combines the
9
+ elegance and expressiveness of the language with the simplicity and power of
10
+ parsing expression grammars.
11
+
12
+ Citrus grammars look very much like Treetop grammars but take a completely
13
+ different approach. Instead of generating parsers from your grammars, Citrus
14
+ evaluates grammars and rules in memory as Ruby modules. In fact, you can even
15
+ define your grammars as Ruby modules in the first place, entirely skipping the
16
+ parsing/evaluation step.
17
+
18
+ Terminals are represented as either strings or regular expressions. Support for
19
+ sequences, choices, labels, repetition, and lookahead (both positive and
20
+ negative) are all included, as well as character classes and the dot-matches-
21
+ anything symbol.
22
+
23
+ To try it out, fire up an IRB session from the root of the project and run one
24
+ of the examples.
25
+
26
+ $ irb -Ilib
27
+ > require 'citrus'
28
+ => true
29
+ > Citrus.load 'examples/calc'
30
+ => [Calc]
31
+ > match = Calc.parse '1 + 5'
32
+ => #<Citrus::Match ...
33
+ > match.value
34
+ => 6
35
+
36
+ Be sure to try requiring `citrus/debug' (instead of just `citrus') if you'd like
37
+ some better visualization of the match results.
38
+
39
+ The code base is very small and it's well-documented and tested, so it should be
40
+ fairly easy to understand for anyone who is familiar with parsing expressions.
41
+
42
+
43
+ ** Links **
44
+
45
+
46
+ http://pdos.csail.mit.edu/~baford/packrat/
47
+ http://en.wikipedia.org/wiki/Parsing_expression_grammar
48
+ http://treetop.rubyforge.org/index.html
49
+
50
+
51
+ ** Installation **
52
+
53
+
54
+ Via RubyGems:
55
+
56
+ $ sudo gem install citrus
57
+
58
+ From a local copy:
59
+
60
+ $ git clone git://github.com/mjijackson/citrus.git
61
+ $ cd citrus
62
+ $ rake package && sudo rake install
63
+
64
+
65
+ ** License **
66
+
67
+
68
+ Copyright 2010 Michael Jackson
69
+
70
+ Permission is hereby granted, free of charge, to any person obtaining a copy
71
+ of this software and associated documentation files (the "Software"), to deal
72
+ in the Software without restriction, including without limitation the rights
73
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
74
+ copies of the Software, and to permit persons to whom the Software is
75
+ furnished to do so, subject to the following conditions:
76
+
77
+ The above copyright notice and this permission notice shall be included in
78
+ all copies or substantial portions of the Software.
79
+
80
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
81
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
82
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
83
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
84
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
85
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
86
+ THE SOFTWARE.
@@ -0,0 +1,67 @@
1
+ require 'rake/clean'
2
+ require 'rake/testtask'
3
+
4
+ task :default => :test
5
+
6
+ # TESTS #######################################################################
7
+
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.test_files = FileList['test/*_test.rb']
10
+ end
11
+
12
+ # DOCS ########################################################################
13
+
14
+ desc "Generate API documentation"
15
+ task :api => 'lib/citrus.rb' do |t|
16
+ output_dir = ENV['OUTPUT_DIR'] || 'api'
17
+ rm_rf output_dir
18
+ sh((<<-SH).gsub(/[\s\n]+/, ' ').strip)
19
+ hanna
20
+ --op #{output_dir}
21
+ --promiscuous
22
+ --charset utf8
23
+ --fmt html
24
+ --inline-source
25
+ --line-numbers
26
+ --accessor option_accessor=RW
27
+ --main Citrus
28
+ --title 'Citrus API Documentation'
29
+ #{t.prerequisites.join(' ')}
30
+ SH
31
+ end
32
+
33
+ CLEAN.include 'api'
34
+
35
+ # PACKAGING & INSTALLATION ####################################################
36
+
37
+ if defined?(Gem)
38
+ $spec = eval("#{File.read('citrus.gemspec')}")
39
+
40
+ directory 'dist'
41
+
42
+ def package(ext='')
43
+ "dist/#{$spec.name}-#{$spec.version}" + ext
44
+ end
45
+
46
+ file package('.gem') => %w< dist > + $spec.files do |f|
47
+ sh "gem build citrus.gemspec"
48
+ mv File.basename(f.name), f.name
49
+ end
50
+
51
+ file package('.tar.gz') => %w< dist > + $spec.files do |f|
52
+ sh "git archive --format=tar HEAD | gzip > #{f.name}"
53
+ end
54
+
55
+ desc "Build packages"
56
+ task :package => %w< .gem .tar.gz >.map {|e| package(e) }
57
+
58
+ desc "Build and install as local gem"
59
+ task :install => package('.gem') do |t|
60
+ sh "gem install #{package('.gem')}"
61
+ end
62
+
63
+ desc "Upload gem to rubygems.org"
64
+ task :release => package('.gem') do |t|
65
+ sh "gem push #{package('.gem')}"
66
+ end
67
+ end
@@ -0,0 +1,29 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'citrus'
3
+ s.version = '1.0.0'
4
+ s.date = '2010-05-13'
5
+
6
+ s.summary = 'Parsing Expressions for Ruby'
7
+ s.description = 'Parsing Expressions for Ruby'
8
+
9
+ s.author = 'Michael Jackson'
10
+ s.email = 'mjijackson@gmail.com'
11
+
12
+ s.require_paths = %w< lib >
13
+
14
+ s.files = Dir['lib/**/*.rb'] +
15
+ Dir['examples/**/*'] +
16
+ Dir['test/*.rb'] +
17
+ %w< citrus.gemspec Rakefile README >
18
+
19
+ s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/ }
20
+
21
+ s.add_dependency('builder')
22
+ s.add_development_dependency('rake')
23
+
24
+ s.has_rdoc = true
25
+ s.rdoc_options = %w< --line-numbers --inline-source --title Citrus --main Citrus >
26
+ s.extra_rdoc_files = %w< README >
27
+
28
+ s.homepage = 'http://github.com/mjijackson/citrus'
29
+ end
@@ -0,0 +1,103 @@
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.
4
+ grammar Calc
5
+ rule term
6
+ (additive | factor) {
7
+ def value
8
+ first.value
9
+ end
10
+ }
11
+ end
12
+
13
+ rule additive
14
+ (factor operator:additive_op term) {
15
+ def value
16
+ operator.apply(factor.value, term.value)
17
+ end
18
+ }
19
+ end
20
+
21
+ rule factor
22
+ (multiplicative | primary) {
23
+ def value
24
+ first.value
25
+ end
26
+ }
27
+ end
28
+
29
+ rule multiplicative
30
+ (primary operator:multiplicative_op factor) {
31
+ def value
32
+ operator.apply(primary.value, factor.value)
33
+ end
34
+ }
35
+ end
36
+
37
+ rule primary
38
+ (term_paren | number) {
39
+ def value
40
+ first.value
41
+ end
42
+ }
43
+ end
44
+
45
+ rule term_paren
46
+ (lparen term rparen) {
47
+ def value
48
+ term.value
49
+ end
50
+ }
51
+ end
52
+
53
+ rule additive_op
54
+ (plus | minus) {
55
+ def apply(factor, term)
56
+ text.strip == '+' ? factor + term : factor - term
57
+ end
58
+ }
59
+ end
60
+
61
+ rule multiplicative_op
62
+ (star | slash) {
63
+ def apply(primary, factor)
64
+ text.strip == '*' ? primary * factor : primary / factor
65
+ end
66
+ }
67
+ end
68
+
69
+ rule number
70
+ (float | integer) {
71
+ def value
72
+ first.value
73
+ end
74
+ }
75
+ end
76
+
77
+ rule float
78
+ ([0-9]+ '.' [0-9]+ space) {
79
+ def value
80
+ text.strip.to_f
81
+ end
82
+ }
83
+ end
84
+
85
+ rule integer
86
+ ([0-9]+ space) {
87
+ def value
88
+ text.strip.to_i
89
+ end
90
+ }
91
+ end
92
+
93
+ rule lparen '(' space end
94
+ rule rparen ')' space end
95
+ rule plus '+' space end
96
+ rule minus '-' space end
97
+ rule star '*' space end
98
+ rule slash '/' space end
99
+
100
+ rule space
101
+ [ \t\n\r]*
102
+ end
103
+ end
@@ -0,0 +1,95 @@
1
+ require 'citrus'
2
+
3
+ # A grammar for mathematical formulas that apply the basic four operations to
4
+ # non-negative numbers (integers and floats), respecting operator precedence and
5
+ # ignoring whitespace.
6
+ module Calc
7
+ include Citrus::Grammar
8
+
9
+ module FirstValue
10
+ def value
11
+ first.value
12
+ end
13
+ end
14
+
15
+ rule :term do
16
+ ext(any(:additive, :factor), FirstValue)
17
+ end
18
+
19
+ rule :additive do
20
+ all(:factor, label(:additive_op, :operator), :term) {
21
+ def value
22
+ operator.apply(factor.value, term.value)
23
+ end
24
+ }
25
+ end
26
+
27
+ rule :factor do
28
+ ext(any(:multiplicative, :primary), FirstValue)
29
+ end
30
+
31
+ rule :multiplicative do
32
+ all(:primary, label(:multiplicative_op, :operator), :factor) {
33
+ def value
34
+ operator.apply(primary.value, factor.value)
35
+ end
36
+ }
37
+ end
38
+
39
+ rule :primary do
40
+ ext(any(:term_paren, :number), FirstValue)
41
+ end
42
+
43
+ rule :term_paren do
44
+ all(:lparen, :term, :rparen) {
45
+ def value
46
+ term.value
47
+ end
48
+ }
49
+ end
50
+
51
+ rule :additive_op do
52
+ any(:plus, :minus) {
53
+ def apply(factor, term)
54
+ text.strip == '+' ? factor + term : factor - term
55
+ end
56
+ }
57
+ end
58
+
59
+ rule :multiplicative_op do
60
+ any(:star, :slash) {
61
+ def apply(primary, factor)
62
+ text.strip == '*' ? primary * factor : primary / factor
63
+ end
64
+ }
65
+ end
66
+
67
+ rule :number do
68
+ ext(any(:float, :integer), FirstValue)
69
+ end
70
+
71
+ rule :float do
72
+ all(/[0-9]+/, '.', /[0-9]+/, :space) {
73
+ def value
74
+ text.strip.to_f
75
+ end
76
+ }
77
+ end
78
+
79
+ rule :integer do
80
+ all(/[0-9]+/, :space) {
81
+ def value
82
+ text.strip.to_i
83
+ end
84
+ }
85
+ end
86
+
87
+ rule :lparen, ['(', :space]
88
+ rule :rparen, [')', :space]
89
+ rule :plus, ['+', :space]
90
+ rule :minus, ['-', :space]
91
+ rule :star, ['*', :space]
92
+ rule :slash, ['/', :space]
93
+
94
+ rule :space, /[ \t\n\r]*/
95
+ end
@@ -0,0 +1,94 @@
1
+ require 'citrus/sugar'
2
+
3
+ # A grammar for mathematical formulas that apply the basic four operations to
4
+ # non-negative numbers (integers and floats), respecting operator precedence and
5
+ # ignoring whitespace.
6
+ Calc = Citrus::Grammar.new {
7
+
8
+ module FirstValue
9
+ def value
10
+ first.value
11
+ end
12
+ end
13
+
14
+ rule term do
15
+ ext(any(additive, factor), FirstValue)
16
+ end
17
+
18
+ rule additive do
19
+ all(factor, label(additive_op, operator), term) {
20
+ def value
21
+ operator.apply(factor.value, term.value)
22
+ end
23
+ }
24
+ end
25
+
26
+ rule factor do
27
+ ext(any(multiplicative, primary), FirstValue)
28
+ end
29
+
30
+ rule multiplicative do
31
+ all(primary, label(multiplicative_op, operator), factor) {
32
+ def value
33
+ operator.apply(primary.value, factor.value)
34
+ end
35
+ }
36
+ end
37
+
38
+ rule primary do
39
+ ext(any(term_paren, number), FirstValue)
40
+ end
41
+
42
+ rule term_paren do
43
+ all(lparen, term, rparen) {
44
+ def value
45
+ term.value
46
+ end
47
+ }
48
+ end
49
+
50
+ rule additive_op do
51
+ any(plus, minus) {
52
+ def apply(factor, term)
53
+ text.strip == '+' ? factor + term : factor - term
54
+ end
55
+ }
56
+ end
57
+
58
+ rule multiplicative_op do
59
+ any(star, slash) {
60
+ def apply(primary, factor)
61
+ text.strip == '*' ? primary * factor : primary / factor
62
+ end
63
+ }
64
+ end
65
+
66
+ rule number do
67
+ ext(any(float, integer), FirstValue)
68
+ end
69
+
70
+ rule float do
71
+ all(/[0-9]+/, '.', /[0-9]+/, space) {
72
+ def value
73
+ text.strip.to_f
74
+ end
75
+ }
76
+ end
77
+
78
+ rule integer do
79
+ all(/[0-9]+/, space) {
80
+ def value
81
+ text.strip.to_i
82
+ end
83
+ }
84
+ end
85
+
86
+ rule lparen, ['(', space]
87
+ rule rparen, [')', space]
88
+ rule plus, ['+', space]
89
+ rule minus, ['-', space]
90
+ rule star, ['*', space]
91
+ rule slash, ['/', space]
92
+
93
+ rule space, /[ \t\n\r]*/
94
+ }