llip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/History.txt +4 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +45 -0
  4. data/README.txt +148 -0
  5. data/Rakefile +66 -0
  6. data/examples/ariteval/ariteval.rb +132 -0
  7. data/examples/ariteval/evaluator.rb +61 -0
  8. data/examples/ariteval/exp.rb +104 -0
  9. data/lib/llip.rb +6 -0
  10. data/lib/llip/abstract_parser.rb +170 -0
  11. data/lib/llip/abstract_scanner.rb +83 -0
  12. data/lib/llip/buffer.rb +35 -0
  13. data/lib/llip/llip_error.rb +43 -0
  14. data/lib/llip/parser.rb +93 -0
  15. data/lib/llip/production_compiler.rb +168 -0
  16. data/lib/llip/production_specification.rb +79 -0
  17. data/lib/llip/recursive_production_compiler.rb +35 -0
  18. data/lib/llip/regexp_abstract_scanner.rb +116 -0
  19. data/lib/llip/regexp_parser.rb +197 -0
  20. data/lib/llip/regexp_scanner.rb +33 -0
  21. data/lib/llip/regexp_specification.rb +210 -0
  22. data/lib/llip/token.rb +47 -0
  23. data/lib/llip/visitable.rb +37 -0
  24. data/spec/ariteval/ariteval_spec.rb +111 -0
  25. data/spec/ariteval/evaluator_spec.rb +106 -0
  26. data/spec/ariteval/exp_spec.rb +232 -0
  27. data/spec/llip/abstract_parser_spec.rb +273 -0
  28. data/spec/llip/abstract_scanner_spec.rb +152 -0
  29. data/spec/llip/buffer_spec.rb +60 -0
  30. data/spec/llip/llip_error_spec.rb +77 -0
  31. data/spec/llip/parser_spec.rb +163 -0
  32. data/spec/llip/production_compiler_spec.rb +271 -0
  33. data/spec/llip/production_specification_spec.rb +75 -0
  34. data/spec/llip/recursive_production_compiler_spec.rb +86 -0
  35. data/spec/llip/regexp_abstract_scanner_spec.rb +320 -0
  36. data/spec/llip/regexp_parser_spec.rb +265 -0
  37. data/spec/llip/regexp_scanner_spec.rb +40 -0
  38. data/spec/llip/regexp_specification_spec.rb +734 -0
  39. data/spec/llip/token_spec.rb +70 -0
  40. data/spec/llip/visitable_spec.rb +38 -0
  41. data/spec/spec_helper.rb +10 -0
  42. metadata +110 -0
@@ -0,0 +1,4 @@
1
+ == 0.1.0 / 2007-06-09
2
+
3
+ * 1 major enhancement
4
+ * Initial release with 100% coverage.
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2005-2007 Matteo Collina
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,45 @@
1
+ History.txt
2
+ MIT-LICENSE
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ examples/ariteval
7
+ examples/ariteval/ariteval.rb
8
+ examples/ariteval/evaluator.rb
9
+ examples/ariteval/exp.rb
10
+ lib/llip
11
+ lib/llip.rb
12
+ lib/llip/abstract_parser.rb
13
+ lib/llip/abstract_scanner.rb
14
+ lib/llip/buffer.rb
15
+ lib/llip/llip_error.rb
16
+ lib/llip/parser.rb
17
+ lib/llip/production_compiler.rb
18
+ lib/llip/production_specification.rb
19
+ lib/llip/recursive_production_compiler.rb
20
+ lib/llip/regexp_abstract_scanner.rb
21
+ lib/llip/regexp_parser.rb
22
+ lib/llip/regexp_scanner.rb
23
+ lib/llip/regexp_specification.rb
24
+ lib/llip/token.rb
25
+ lib/llip/visitable.rb
26
+ spec/ariteval
27
+ spec/ariteval/ariteval_spec.rb
28
+ spec/ariteval/evaluator_spec.rb
29
+ spec/ariteval/exp_spec.rb
30
+ spec/llip
31
+ spec/llip/abstract_parser_spec.rb
32
+ spec/llip/abstract_scanner_spec.rb
33
+ spec/llip/buffer_spec.rb
34
+ spec/llip/llip_error_spec.rb
35
+ spec/llip/parser_spec.rb
36
+ spec/llip/production_compiler_spec.rb
37
+ spec/llip/production_specification_spec.rb
38
+ spec/llip/recursive_production_compiler_spec.rb
39
+ spec/llip/regexp_abstract_scanner_spec.rb
40
+ spec/llip/regexp_parser_spec.rb
41
+ spec/llip/regexp_scanner_spec.rb
42
+ spec/llip/regexp_specification_spec.rb
43
+ spec/llip/token_spec.rb
44
+ spec/llip/visitable_spec.rb
45
+ spec/spec_helper.rb
@@ -0,0 +1,148 @@
1
+ = LLInterpretedParser
2
+
3
+ The LL(k) Interpreted Parser (llip) is an automated tool to easily create an LL(k) parser and the related scanner without the need of generating anything.
4
+ Everything is done on the fly through a simple DSL.
5
+
6
+ == A Little comparrison against other tools
7
+
8
+ Tools like JavaCC, ANTLR, Coco/R and others use an external description file which they compile into the destination code.
9
+ This file it's usually written using a complex product related language. Using Ruby metaprogramming, a parser generator can go one step further.
10
+ In fact, the llip gem gives you the possibility to write a parser writing only Ruby code.
11
+
12
+ == Don't compile anything
13
+
14
+ This tool is based on a simple and powerful DSL that can be used to specify:
15
+ * some tokens to be recognized in the form of regular expressions, as defined in LLIP::RegexpParser,
16
+ * some productions using LLIP::ProductionSpecification,
17
+ * some LL(K) related behaviour like lookaheads.
18
+
19
+ Everything specified is automatically translated into live objects which can be used to do LL(K) parsing.
20
+
21
+ == The LLIP Library
22
+
23
+ The LLIP::Parser is a facade of the entire library. In fact it handles all the wiring to make it work.
24
+ It also takes care of generating the right LLIP::TokenSpecification starting from its definition,
25
+ which is a simple string written as defined in LLIP::RegexpParser.
26
+
27
+ To use this library it's necessary to subclass LLIP:Parser and so it's possible to specify all the needed behaviours.
28
+ An instance of that subclass gains the +parse+ method, which parses a string or an IO with the productions previously defined.
29
+
30
+ == Installation
31
+
32
+ <code>sudo gem install llip</code>
33
+
34
+ == History of this gem
35
+
36
+ This library was originally developed as a project for a computer language course at the engeneering[http://www.ing.unibo.it] faculty of the university of Bologna, Italy.
37
+
38
+ == A Simple Example
39
+
40
+ #! /usr/bin/ruby
41
+
42
+ require 'rubygems'
43
+
44
+ require 'llip'
45
+
46
+ class MyParser < LLIP::Parser
47
+
48
+ token :num, "(1|2|3|4|5|6|7|8|9|0)+" # simple definition of a number
49
+
50
+ scope :number # definition of the scope, the first production which will be called
51
+
52
+ production(:number) do |p|
53
+
54
+ # inside the :number production,
55
+ # we are specifying what to do when we encounter a :num token
56
+ p.token :num do |result,scanner,parser|
57
+ puts "The number is..."
58
+ number = scanner.current.value
59
+ puts number
60
+ scanner.next # we are reading the next token
61
+ number
62
+ end
63
+
64
+ end
65
+ end
66
+
67
+ puts "--->>> Example 1"
68
+
69
+ parser = MyParser.new
70
+
71
+ parser.parse("1")
72
+
73
+ parser.parse("34065")
74
+
75
+ parser.parse("123456")
76
+
77
+ puts "--->>> Example 2"
78
+
79
+ begin
80
+ parser.parse("3+2")
81
+ rescue LLIP::LLIPError => error
82
+ puts error
83
+ end
84
+
85
+ class MyParser
86
+
87
+ token :plus, '\+' # the '\' is required because it escapes the '+',
88
+ # which is a token of a regular expressions.
89
+
90
+ scope :exp
91
+
92
+ production :exp, :recursive do |p|
93
+ p.default { |scanner,parser| parser.parse_number } # this block is exectued before any
94
+ # other block. The result of this block
95
+ # will be putted in the first parameter of the
96
+ # first production matched.
97
+
98
+ p.token :plus do |left,scanner,parser|
99
+ scanner.next
100
+ right = parser.parse_number # we are calling another production!!
101
+ sum = left.to_i + right.to_i
102
+ puts "The sum is #{sum}"
103
+ sum
104
+ end
105
+ end
106
+ end
107
+
108
+ result = parser.parse "3+2+4"
109
+
110
+ puts "the result is #{result}"
111
+
112
+ == A more complex example, the Ariteval parser
113
+
114
+ Bundled with this library there is an example of an Arithmetic Evaluator,
115
+ which evaluates simple expressions like "3-7*(6-2)". In the "examples/ariteval" directory there are:
116
+ [<b>exp.rb</b>] contains all the Abstract Syntax Tree node definitions.
117
+ [<b>ariteval.rb</b>] contains the Ariteval class which defines all the productions using LLIP::Parser.
118
+ [<b>evaluator.rb</b>] a simple visitor which uses the classes defined in exp.rb.
119
+
120
+ == Author
121
+
122
+ This library has been written by Matteo Collina, matteo dot collina at gmail dot com.
123
+
124
+ == License
125
+
126
+ (The MIT license)
127
+
128
+ Copyright (c) 2006-2007 Matteo Collina
129
+
130
+ Permission is hereby granted, free of charge, to any person obtaining
131
+ a copy of this software and associated documentation files (the
132
+ "Software"), to deal in the Software without restriction, including
133
+ without limitation the rights to use, copy, modify, merge, publish,
134
+ distribute, sublicense, and/or sell copies of the Software, and to
135
+ permit persons to whom the Software is furnished to do so, subject to
136
+ the following conditions:
137
+
138
+ The above copyright notice and this permission notice shall be
139
+ included in all copies or substantial portions of the Software.
140
+
141
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
142
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
143
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
144
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
145
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
146
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
147
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
148
+
@@ -0,0 +1,66 @@
1
+ require 'rake'
2
+ require 'hoe'
3
+ require 'lib/llip'
4
+ require 'spec/rake/spectask'
5
+ require 'spec/rake/verify_rcov'
6
+ require 'iconv'
7
+
8
+ Hoe.new('llip',LLIP::VERSION) do |p|
9
+ p.author = "Matteo Collina"
10
+ p.name = "llip"
11
+ p.email = "matteo.collina@gmail.com"
12
+ p.extra_deps = ["rspec",">= 1.0.0"]
13
+ p.description = p.paragraphs_of('README.txt', 1..3).join("\n\n")
14
+ p.summary = "LLIP is a tool to geneate a LL(k) parser."
15
+ p.url = "http://llip.rubyforge.org"
16
+ p.rdoc_pattern = /(^lib\/llip\/.*|.*\.txt|^examples\/ariteval\/.*)/
17
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
18
+ end
19
+
20
+ Rake.application["default"].prerequisites.shift
21
+
22
+ task :default => [:spec]
23
+
24
+ desc "Run the LLIP specifications"
25
+ Spec::Rake::SpecTask.new('spec') do |t|
26
+ t.spec_files = FileList['spec/llip/*.rb']
27
+ t.spec_opts = ["--diff c"]
28
+ end
29
+
30
+ desc "Run all the specifications"
31
+ Spec::Rake::SpecTask.new('spec:all') do |t|
32
+ t.spec_files = FileList['spec/**/*.rb'] - ["specs/spec_helper.rb"]
33
+ t.spec_opts = ["--diff c"]
34
+ end
35
+
36
+ desc "Run all the specifications and generate the output in html"
37
+ Spec::Rake::SpecTask.new('spec:html') do |t|
38
+ t.spec_files = FileList['spec/**/*.rb'] - ["specs/spec_helper.rb"]
39
+ t.spec_opts = ["-f html","--diff c","-o","specs.html"]
40
+ end
41
+
42
+ desc "Run all the specification with RCov support"
43
+ Spec::Rake::SpecTask.new('rcov') do |t|
44
+ t.spec_files = FileList['spec/**/*.rb'] - ["specs/spec_helper.rb"]
45
+ t.spec_opts = ["-c"]
46
+ t.rcov = true
47
+ t.rcov_opts = ['--exclude', "rcov,spec,gem"]
48
+ end
49
+
50
+ desc "Run all the specification and checks if the coverage is at the threshold"
51
+ RCov::VerifyTask.new(:verify_rcov => :rcov) do |t|
52
+ t.threshold = 100.0
53
+ end
54
+
55
+ desc "Creates the Manifest"
56
+ task :create_manifest do
57
+ files = FileList["{examples,lib,spec}/**/*"] + ["README.txt","Manifest.txt","History.txt","MIT-LICENSE","Rakefile"]
58
+ files.sort!
59
+ File.open("Manifest.txt", "w") do |io|
60
+ io.write(files.join("\n"))
61
+ end
62
+ end
63
+
64
+ task :commit => :verify_rcov do |t|
65
+ exec "svn commit"
66
+ end
@@ -0,0 +1,132 @@
1
+ require 'parser'
2
+ require 'evaluator'
3
+
4
+ # It's a simple arithmetical evaluator. It's able to parse expressions like these:
5
+ # * ( a = 3 * 2 ) - ( 24 + a ),
6
+ # * 3 * (4 - 2) + 5*(4/2)/(3-2).
7
+ #
8
+ # It realizes the following grammar:
9
+ #
10
+ # non terminal symbols = { SCOPE, EXP, TERM, POW, FACTOR, NUMBER }
11
+ # terminal symbols = any number, the charachters " , . + - * / ^ = [ ] ( ) ' "
12
+ #
13
+ # an id is an unlimited string composed of every charachter "a".."Z" and the "_"
14
+ #
15
+ # P = {
16
+ # SCOPE ::= EXP
17
+ # EXP ::= EXP + TERM
18
+ # EXP ::= EXP - TERM
19
+ # TERM ::= POW
20
+ # TERM ::= TERM * FACTOR
21
+ # TERM ::= TERM / FACTOR
22
+ # FACTOR ::= any sequence of 0,1,2,3,4,5,6,7,8,9
23
+ # FACTOR ::= an id
24
+ # FACTOR ::= an id =
25
+ # FACTOR ::= ( EXP )
26
+ # }
27
+ #
28
+ class Ariteval < Parser
29
+
30
+ def initialize
31
+ super
32
+ @evaluator = Evaluator.new
33
+ end
34
+
35
+ def evaluate(source)
36
+ parse(source).accept(@evaluator)
37
+ @evaluator.result
38
+ end
39
+
40
+ # tokens definitions
41
+
42
+ numbers = ("0".."9").to_a.join("|")
43
+ token :number, "(#{numbers})+ *"
44
+
45
+ token :plus, '\+ *'
46
+
47
+ token :minus, '- *'
48
+
49
+ token :mul, '\* *'
50
+
51
+ token :div, '/ *'
52
+
53
+ token "(".to_sym, '\( *'
54
+
55
+ token ")".to_sym, '\) *'
56
+
57
+ identifiers = (("a".."z").to_a + ("A".."Z").to_a).join("|")
58
+ token :ident, "(#{identifiers}) *"
59
+
60
+ token :assign, "= *"
61
+
62
+ # production definitions
63
+
64
+ lookahead(true)
65
+
66
+ scope :exp
67
+
68
+ production(:exp,:recursive) do |prod|
69
+ prod.default { |scanner,parser| parser.parse_term }
70
+
71
+ prod.token(:plus) do |term_seq,scanner,parser|
72
+ scanner.next
73
+ next_term = parser.parse_term
74
+ PlusExp.new(term_seq,next_term)
75
+ end
76
+
77
+ prod.token(:minus) do |term_seq,scanner,parser|
78
+ scanner.next
79
+ next_term = parser.parse_term
80
+ MinusExp.new(term_seq,next_term)
81
+ end
82
+
83
+ end
84
+
85
+ production(:term,:recursive) do |prod|
86
+ prod.default { |scanner,parser| parser.parse_factor }
87
+
88
+ prod.token(:mul) do |factor_seq,scanner,parser|
89
+ scanner.next
90
+ next_factor = parser.parse_factor
91
+ MulExp.new(factor_seq,next_factor)
92
+ end
93
+
94
+ prod.token(:div) do |factor_seq,scanner,parser|
95
+ scanner.next
96
+ next_factor = parser.parse_factor
97
+ DivExp.new(factor_seq,next_factor)
98
+ end
99
+ end
100
+
101
+ production(:factor,:single) do |prod|
102
+
103
+ prod.token(:number) do |result,scanner|
104
+ current = scanner.current
105
+ scanner.next
106
+ NumExp.new(current.value.to_i)
107
+ end
108
+
109
+ prod.token("(".to_sym) do |result,scanner,parser|
110
+ scanner.next
111
+ result = parser.parse_exp
112
+ parser.raise "Every '(' must be followed by a ')'" unless scanner.current == ")".to_sym
113
+ scanner.next
114
+ result
115
+ end
116
+
117
+ prod.token(:ident,:assign) do |result,scanner,parser|
118
+ name = scanner.current.to_s.strip
119
+ scanner.next
120
+ scanner.next
121
+ AssignIdentExp.new(name,parser.parse_exp)
122
+ end
123
+
124
+ prod.token(:ident) do |result,scanner,parser|
125
+ result = IdentExp.new(scanner.current.to_s.strip)
126
+ scanner.next
127
+ result
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,61 @@
1
+ # It's a symple visitor for the abstract syntax tree created with the nodes in exp.rb.
2
+ # It evaluates the expressions in the obvious way.
3
+ class Evaluator
4
+
5
+ attr_reader :ident_table
6
+
7
+ def initialize
8
+ @result = 0
9
+ @ident_table = {}
10
+ end
11
+
12
+ def visit_plus_exp(exp)
13
+ left,right = left_and_right(exp)
14
+ @result = left + right
15
+ end
16
+
17
+ def visit_minus_exp(exp)
18
+ left,right = left_and_right(exp)
19
+ @result = left - right
20
+ end
21
+
22
+ def visit_mul_exp(exp)
23
+ left,right = left_and_right(exp)
24
+ @result = left * right
25
+ end
26
+
27
+ def visit_div_exp(exp)
28
+ left,right = left_and_right(exp)
29
+ @result = left / right
30
+ end
31
+
32
+ def visit_num_exp(exp)
33
+ @result = exp.value
34
+ end
35
+
36
+ def visit_assign_ident_exp(exp)
37
+ exp.value.accept(self)
38
+ ident_table[exp.name] = @result
39
+ end
40
+
41
+ def visit_ident_exp(exp)
42
+ @result = ident_table[exp.value]
43
+ end
44
+
45
+ def result
46
+ result = @result
47
+ @result = 0
48
+ return result
49
+ end
50
+
51
+ private
52
+
53
+ def left_and_right(exp)
54
+ exp.left.accept(self)
55
+ left = @result
56
+
57
+ exp.right.accept(self)
58
+ right = @result
59
+ [left,right]
60
+ end
61
+ end