citrus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }