rpdf2txt 0.8.2

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.
Files changed (127) hide show
  1. data/History.txt +5 -0
  2. data/LICENCE +515 -0
  3. data/Manifest.txt +126 -0
  4. data/README.txt +30 -0
  5. data/Rakefile +24 -0
  6. data/bin/rpdf2txt +58 -0
  7. data/config.save +12 -0
  8. data/install.rb +1098 -0
  9. data/lib/rpdf2txt-rockit/base_extensions.rb +73 -0
  10. data/lib/rpdf2txt-rockit/bootstrap.rb +120 -0
  11. data/lib/rpdf2txt-rockit/bounded_lru_cache.rb +43 -0
  12. data/lib/rpdf2txt-rockit/conflict_resolution.rb +302 -0
  13. data/lib/rpdf2txt-rockit/directed_graph.rb +401 -0
  14. data/lib/rpdf2txt-rockit/glr_parser.rb +393 -0
  15. data/lib/rpdf2txt-rockit/grammar.rb +644 -0
  16. data/lib/rpdf2txt-rockit/graphdrawing.rb +107 -0
  17. data/lib/rpdf2txt-rockit/graphviz_dot.rb +63 -0
  18. data/lib/rpdf2txt-rockit/indexable.rb +53 -0
  19. data/lib/rpdf2txt-rockit/lalr_parsetable_generator.rb +144 -0
  20. data/lib/rpdf2txt-rockit/parse_table.rb +273 -0
  21. data/lib/rpdf2txt-rockit/parsetable_generation.rb +164 -0
  22. data/lib/rpdf2txt-rockit/parsing_ambiguities.rb +84 -0
  23. data/lib/rpdf2txt-rockit/profiler.rb +168 -0
  24. data/lib/rpdf2txt-rockit/reduce_actions_generator.rb +523 -0
  25. data/lib/rpdf2txt-rockit/rockit.rb +76 -0
  26. data/lib/rpdf2txt-rockit/rockit_grammar_ast_eval.rb +187 -0
  27. data/lib/rpdf2txt-rockit/rockit_grammars_parser.rb +126 -0
  28. data/lib/rpdf2txt-rockit/sourcecode_dumpable.rb +181 -0
  29. data/lib/rpdf2txt-rockit/stringscanner.rb +54 -0
  30. data/lib/rpdf2txt-rockit/syntax_tree.rb +452 -0
  31. data/lib/rpdf2txt-rockit/token.rb +364 -0
  32. data/lib/rpdf2txt-rockit/version.rb +3 -0
  33. data/lib/rpdf2txt/attributesparser.rb +42 -0
  34. data/lib/rpdf2txt/cmapparser.rb +65 -0
  35. data/lib/rpdf2txt/data/_cmap.grammar +11 -0
  36. data/lib/rpdf2txt/data/_cmap_range.grammar +15 -0
  37. data/lib/rpdf2txt/data/_pdfattributes.grammar +32 -0
  38. data/lib/rpdf2txt/data/cmap.grammar +11 -0
  39. data/lib/rpdf2txt/data/cmap.rb +37 -0
  40. data/lib/rpdf2txt/data/cmap_range.grammar +15 -0
  41. data/lib/rpdf2txt/data/cmap_range.rb +43 -0
  42. data/lib/rpdf2txt/data/fonts/Courier-Bold.afm +342 -0
  43. data/lib/rpdf2txt/data/fonts/Courier-BoldOblique.afm +342 -0
  44. data/lib/rpdf2txt/data/fonts/Courier-Oblique.afm +342 -0
  45. data/lib/rpdf2txt/data/fonts/Courier.afm +342 -0
  46. data/lib/rpdf2txt/data/fonts/Helvetica-Bold.afm +2827 -0
  47. data/lib/rpdf2txt/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  48. data/lib/rpdf2txt/data/fonts/Helvetica-Oblique.afm +3051 -0
  49. data/lib/rpdf2txt/data/fonts/Helvetica.afm +3051 -0
  50. data/lib/rpdf2txt/data/fonts/License-Adobe.txt +65 -0
  51. data/lib/rpdf2txt/data/fonts/Symbol.afm +213 -0
  52. data/lib/rpdf2txt/data/fonts/Times-Bold.afm +2588 -0
  53. data/lib/rpdf2txt/data/fonts/Times-BoldItalic.afm +2384 -0
  54. data/lib/rpdf2txt/data/fonts/Times-Italic.afm +2667 -0
  55. data/lib/rpdf2txt/data/fonts/Times-Roman.afm +2419 -0
  56. data/lib/rpdf2txt/data/fonts/ZapfDingbats.afm +225 -0
  57. data/lib/rpdf2txt/data/pdfattributes.grammar +32 -0
  58. data/lib/rpdf2txt/data/pdfattributes.rb +71 -0
  59. data/lib/rpdf2txt/data/pdftext.grammar +102 -0
  60. data/lib/rpdf2txt/data/pdftext.rb +146 -0
  61. data/lib/rpdf2txt/default_handler.rb +352 -0
  62. data/lib/rpdf2txt/lzw.rb +69 -0
  63. data/lib/rpdf2txt/object.rb +1114 -0
  64. data/lib/rpdf2txt/parser.rb +169 -0
  65. data/lib/rpdf2txt/symbol.rb +408 -0
  66. data/lib/rpdf2txt/text.rb +182 -0
  67. data/lib/rpdf2txt/text_state.rb +434 -0
  68. data/lib/rpdf2txt/textparser.rb +42 -0
  69. data/test/data/3392_obj +0 -0
  70. data/test/data/397_decrypted +15 -0
  71. data/test/data/450_decrypted +153 -0
  72. data/test/data/450_obj +0 -0
  73. data/test/data/452_decrypted +125 -0
  74. data/test/data/454_decrypted +108 -0
  75. data/test/data/456_decrypted +106 -0
  76. data/test/data/458_decrypted +111 -0
  77. data/test/data/458_obj +0 -0
  78. data/test/data/460_decrypted +118 -0
  79. data/test/data/460_obj +0 -0
  80. data/test/data/463_decrypted +117 -0
  81. data/test/data/465_decrypted +107 -0
  82. data/test/data/465_obj +0 -0
  83. data/test/data/90_obj +0 -0
  84. data/test/data/90_obj_comp +1 -0
  85. data/test/data/decrypted +0 -0
  86. data/test/data/encrypt_obj +0 -0
  87. data/test/data/encrypt_string +0 -0
  88. data/test/data/encrypt_string_128bit +0 -0
  89. data/test/data/encrypted_object_stream.pdf +0 -0
  90. data/test/data/firststream +1 -0
  91. data/test/data/index.pdfobj +0 -0
  92. data/test/data/index_2bit.pdfobj +0 -0
  93. data/test/data/index_masked.pdfobj +0 -0
  94. data/test/data/indexed.pdfobj +0 -0
  95. data/test/data/indexed_2bit.pdfobj +0 -0
  96. data/test/data/indexed_masked.pdfobj +0 -0
  97. data/test/data/inline.png +0 -0
  98. data/test/data/logo.png +0 -0
  99. data/test/data/lzw.pdfobj +0 -0
  100. data/test/data/lzw_index.pdfobj +0 -0
  101. data/test/data/page_tree.pdf +148 -0
  102. data/test/data/pdf_20.png +0 -0
  103. data/test/data/pdf_21.png +0 -0
  104. data/test/data/pdf_22.png +0 -0
  105. data/test/data/pdf_50.png +0 -0
  106. data/test/data/png.pdfobj +0 -0
  107. data/test/data/space_bug_stream.txt +119 -0
  108. data/test/data/stream.txt +292 -0
  109. data/test/data/stream_kerning_bug.txt +13 -0
  110. data/test/data/stream_kerning_bug2.txt +6 -0
  111. data/test/data/test.pdf +0 -0
  112. data/test/data/test.txt +8 -0
  113. data/test/data/test_text.txt +42 -0
  114. data/test/data/working_obj +0 -0
  115. data/test/data/working_obj2 +0 -0
  116. data/test/mock.rb +149 -0
  117. data/test/suite.rb +30 -0
  118. data/test/test_pdf_object.rb +1802 -0
  119. data/test/test_pdf_parser.rb +1340 -0
  120. data/test/test_pdf_text.rb +789 -0
  121. data/test/test_space_bug_05_2004.rb +87 -0
  122. data/test/test_stream.rb +194 -0
  123. data/test/test_text_state.rb +315 -0
  124. data/usage-en.txt +112 -0
  125. data/user-stories/UserStories_Rpdf2Txt.txt +34 -0
  126. data/user-stories/documents/swissmedicjournal/04_2004.pdf +0 -0
  127. metadata +220 -0
@@ -0,0 +1,73 @@
1
+ class Array
2
+ def equality_uniq
3
+ uniq_elements = []
4
+ self.each {|e| uniq_elements.push(e) unless uniq_elements.index(e)}
5
+ uniq_elements
6
+ end
7
+
8
+ def delete_at_indices(indices = [])
9
+ not_deleted = Array.new
10
+ self.each_with_index {|e,i| not_deleted.push(e) if !indices.include?(i)}
11
+ not_deleted
12
+ end
13
+ end
14
+
15
+ class DefaultInitArray < Array
16
+ def initialize(*args, &initblock)
17
+ super(*args)
18
+ @initblock = initblock
19
+ end
20
+
21
+ def [](index)
22
+ super(index) || (self[index] = @initblock.call(index))
23
+ end
24
+ end
25
+
26
+ class ArrayOfArrays < DefaultInitArray
27
+ @@create_array = proc{|i| Array.new}
28
+ def initialize(*args)
29
+ super(*args, &@@create_array)
30
+ end
31
+ end
32
+
33
+ class ArrayOfHashes < DefaultInitArray
34
+ @@create_hash = proc{|i| Hash.new}
35
+ def initialize(*args)
36
+ super(*args, &@@create_hash)
37
+ end
38
+ end
39
+
40
+ # Hash which takes a block that is called to give a default value when a key
41
+ # has the value nil in the hash.
42
+ class DefaultInitHash < Hash
43
+ def initialize(*args, &initblock)
44
+ super(*args)
45
+ @initblock = initblock
46
+ end
47
+
48
+ def [](key)
49
+ #super(key) || (self[key] = @initblock.call(key))
50
+ fetch(key) {
51
+ store(key, @initblock.call(key))
52
+ }
53
+ end
54
+ end
55
+
56
+ unless Object.constants.include?("TimesClass")
57
+ TimesClass = (RUBY_VERSION < "1.7") ? Time : Process
58
+ end
59
+
60
+ def time_and_puts(string, &block)
61
+ if $TIME_AND_PUTS_VERBOSE
62
+ print string; STDOUT.flush
63
+ end
64
+ starttime = [Time.new, TimesClass.times]
65
+ block.call
66
+ endtime = [Time.new, TimesClass.times]
67
+ duration = endtime[0] - starttime[0]
68
+ begin
69
+ load = [((endtime[1].utime+endtime[1].stime)-(starttime[1].utime+starttime[1].stime))/duration*100.0, 100.0].min
70
+ puts " (%.2f s %.2f%%)" % [duration, load] if $TIME_AND_PUTS_VERBOSE
71
+ rescue FloatDomainError
72
+ end
73
+ end
@@ -0,0 +1,120 @@
1
+ # This is a version of the grammar for Rockit grammars in code. It is used
2
+ # to bootstrap the rockit grammar parser.
3
+ #
4
+ # NOTE: The handcoded grammar in this file may not be
5
+ # fully in sync with the rockit-grammar.grammar. This is not
6
+ # needed as long as it can be used to parse the file. However,
7
+ # YOU SHOULD NOT USE THE HANDCODED GRAMMAR IN THIS FILE AS A REFERENCE
8
+ # TO ROCKIT GRAMMARS.
9
+ #
10
+ # Overview of bootstrapping process:
11
+ # 1. Generate a parser (rp) for Rockit grammars from this in-code grammar.
12
+ # The parser MAY NOT BE FULLY UP-TO-DATE but it must be able to parse
13
+ # the rockit-grammar.grammar file.
14
+ # 2. Parse the rockit-grammar.grammar file with rp.
15
+ # 3. Generate source code for up-to-date rockit parser.
16
+ #
17
+ require 'rpdf2txt-rockit/rockit'
18
+ require 'rpdf2txt-rockit/base_extensions'
19
+
20
+ module Parse
21
+ RockitTokens = [
22
+ blank = t("Blank", /\s+/n, :Skip),
23
+ comment = t("Comment", /#.*$/n, :Skip),
24
+ string = t("String", /('((\\')|[^'])*')|("((\\")|[^"])*")/n),
25
+ regexp = t("Regexp", /\/((\\\/)|[^\/])*\/[iomx]*/n),
26
+ arrow = t("Arrow", /(->)|(::=)|(:)/n),
27
+ symbol_name = t("SymbolName", /[A-Z][A-Za-z]*/n),
28
+ production_reference = t("ProductionReference", /[A-Z][A-Za-z]*\d+/n)
29
+ ]
30
+
31
+ RockitTokenProds = [
32
+ prod(:Tokens, ['Tokens', plus(:TokenSpec)], stb(:^, [:_, :tokens])),
33
+ prod(:TokenSpec,
34
+ [symbol_name, '=', ore(string, regexp), maybe(:TokenOpts)],
35
+ stb(nil, [:tokenname, :_, :regexp, :options])),
36
+ prod(:TokenOpts, ['[', /:Skip/in, ']'], stb(:^, [:_, :options, :_]))
37
+ ]
38
+
39
+ def rockit_tokens_parser
40
+ Parse.parser_from_grammar(Grammar.new("RockitTokens", RockitTokenProds,
41
+ RockitTokens))
42
+ end
43
+
44
+ RockitPrioritiesProds = [
45
+ prod(:Priorities, ['Priorities', plus(:Priority)], stb(:^, [:_, :prios])),
46
+ prod(:Priority, [ore('left(', 'right('), liste(:ProdRef, ','), ')'],
47
+ stb(:Associativity, [:relation, :productionrefs, :_])),
48
+ prod(:Priority, [:ProdRef, plus(ore('>', '='), :ProdRef)],
49
+ stb(:Precedence, [:first, :rest])),
50
+ prod(:Priority, [:Priority, ','], stb(:^, [:prio, :_])),
51
+ prod(:ProdRef, [ore(symbol_name, production_reference)], stb(:^))
52
+ ]
53
+
54
+ def rockit_priorities_parser
55
+ Parse.parser_from_grammar(Grammar.new("RockitPriorities",
56
+ RockitPrioritiesProds,
57
+ RockitTokens))
58
+ end
59
+
60
+ RockitProductionsProds = [
61
+ prod(:Productions, ['Productions', plus(:Prod)],
62
+ stb(:Productions, [:_, :productions])),
63
+ prod(:Prod, [symbol_name, arrow, liste(:Alt, '|')],
64
+ stb(nil, [:nonterminal, :_, :alts])),
65
+ prod(:Alt, [plus(:Element), maybe(:AstSpec)],
66
+ stb(nil, [:elements, :astspec])),
67
+ prod(:Element, [symbol_name], stb(:^)),
68
+ prod(:Element, [ore(string, regexp)], stb(:ImplicitToken, [:regexp])),
69
+ prod(:Element, [:Element, '?'], stb(:Maybe, [:element, :_])),
70
+ prod(:Element, [:Element, '+'], stb(:Plus, [:element, :_])),
71
+ prod(:Element, [:Element, '*'], stb(:Mult, [:element, :_])),
72
+ prod(:Element, ['(', liste(:Element, '|'), ')'],
73
+ stb(:Or, [:_,:elements, :_])),
74
+ prod(:Element, ['(', plus(:Element), ')'],
75
+ stb(:Sequence, [:_, :elements, :_])),
76
+ prod(:Element, ['list(', :Element, ',', :Element, ')'],
77
+ stb(:List, [:_, :element, :_, :delimiter])),
78
+ prod(:AstSpec, ['[', maybe(:ProdSpec), maybe(:ElemSpecs), ']'],
79
+ stb(nil, [:_, :prodspec, :elemspecs, :_])),
80
+ prod(:ElemSpecs, [': ', liste(:ElemSpec, ',')], stb(:^, [:_, :specs])),
81
+ prod(:ElemSpec, [ore(/[a-z]+/n, '_')], stb(:^)),
82
+ prod(:ProdSpec, [ore(symbol_name, '^')], stb(:^, [:name])),
83
+ ]
84
+
85
+ def rockit_productions_parser
86
+ Parse.parser_from_grammar(Grammar.new("RockitProductions",
87
+ RockitProductionsProds,
88
+ RockitTokens))
89
+ end
90
+ module_function :rockit_productions_parser
91
+
92
+ RockitProds = [
93
+ prod(:Grammar,
94
+ ['Grammar', /[A-Za-z]+([-_]*[A-Za-z\d]+)*/n,
95
+ maybe(:Tokens), :Productions, maybe(:Priorities)],
96
+ stb(:Grammar, [:_, :language, :tokens, :productions, :priorities]))
97
+ ] + RockitTokenProds + RockitProductionsProds + RockitPrioritiesProds
98
+
99
+ def rockit_grammars_bootstrap_parser
100
+ Parse.parser_from_grammar(Grammar.new("RockitGrammar",
101
+ RockitProds, RockitTokens))
102
+ end
103
+ module_function :rockit_grammars_bootstrap_parser
104
+ end
105
+
106
+ if __FILE__ == $0
107
+ $TIME_AND_PUTS_VERBOSE = true
108
+ grammarfile = ARGV[0] || "rockit-grammar.grammar"
109
+ parser_filename = ARGV[1] || "rockit_grammars_parser.rb"
110
+ module_name = ARGV[3] || "Parse"
111
+ parser_name = as_module_method_named(module_name,
112
+ ARGV[2] || (ARGV[0] ? "parser" :
113
+ "rockit_grammars_parser"))
114
+ time_and_puts("Boostrapping by generating parser from handcoded grammar") {
115
+ $bootstrap_parser = Parse.rockit_grammars_bootstrap_parser
116
+ }
117
+ Parse.generate_parser_from_file_to_file(grammarfile, parser_filename,
118
+ parser_name, module_name,
119
+ $bootstrap_parser)
120
+ end
@@ -0,0 +1,43 @@
1
+ # Cache as hash with bounded size. Will delete least recently used (LRU) entry
2
+ # if full when new key-value pair added.
3
+ # NOTE: Not thread safe...
4
+ class BoundedLruCache
5
+ attr_accessor :max_size
6
+
7
+ def initialize(max_size = 2**30-1, anObject = nil)
8
+ @hash, @uses, @max_size = Hash.new(anObject), Array.new, max_size
9
+ end
10
+
11
+ def []=(key, val)
12
+ delete_least_recently_used if @hash.length >= @max_size
13
+ latest_used_key(key)
14
+ @hash[key] = val
15
+ end
16
+
17
+ def [](key)
18
+ res = @hash[key]
19
+ latest_used_key(key) if res
20
+ res
21
+ end
22
+
23
+ # Delegate undefined methods to the hash
24
+ def method_missing(methodId, *args)
25
+ if @hash.respond_to?(methodId)
26
+ @hash.send(methodId, *args)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def latest_used_key(key)
35
+ @uses.delete(key)
36
+ @uses.push(key)
37
+ end
38
+
39
+ def delete_least_recently_used
40
+ lru = @uses.shift
41
+ @hash.delete lru
42
+ end
43
+ end
@@ -0,0 +1,302 @@
1
+ require 'rpdf2txt-rockit/sourcecode_dumpable'
2
+ # A ProductionPriorities object knows the relative priorities between
3
+ # productions and can resolve which subTreeProducitons are valid at
4
+ # certain positions in a production. In essence it is a partial ordering
5
+ # among the productions of a grammar.
6
+ #
7
+ # For more info about this kind of tree filtering conflict resolution
8
+ # see Eelco Visser's thesis from 1997.
9
+ #
10
+ # IDEA: Change to a more general handling of associativity using associativity
11
+ # vectors � la Ziemovit Laski's Bertha parser gen. Each production/rule has
12
+ # an assoc. vector with ones in the positions were subtrees having the same
13
+ # precedence is allowed. Left associativity is thus expressed as an assoc.
14
+ # vector with a one in the first position and zeros in the rest. Right
15
+ # associativity is zeros in all but the last position etc. See the papers
16
+ # on Bertha on www.ziemovitlaski.net.
17
+ #
18
+ Relation = Struct.new("Relation", :left, :relation, :right)
19
+ class Relation
20
+ include SourceCodeDumpable
21
+
22
+ def inspect
23
+ "#{relation.inspect}(#{left.inspect}, #{right.inspect})"
24
+ end
25
+
26
+ def to_src(name = nil, nameHash = {})
27
+ left_src = nameHash[left] ? nameHash[left] : left.to_src(nil, nameHash)
28
+ right_src = nameHash[right] ? nameHash[right] : right.to_src(nil, nameHash)
29
+ assign_to(name,
30
+ new_of_my_type(as_code(left_src),
31
+ as_code(relation.inspect),
32
+ as_code(right_src)))
33
+ end
34
+ end
35
+ class RelationCircularityException < Exception; end
36
+
37
+ class ProductionPriorities
38
+ include SourceCodeDumpable
39
+
40
+ def initialize(relations = [])
41
+ @left, @right, @non_associativity = Hash.new, Hash.new, Hash.new
42
+ @higher, @equal = Hash.new(nil), Hash.new(nil)
43
+ @in_conflict, @transitivity_stack = Hash.new(false), []
44
+ init_relations(relations)
45
+ end
46
+
47
+ # Precedence relations can be :HIGHER, :EQUAL or :LOWER. When not set the
48
+ # (default) relation is a non-relation, ie. the productions are not related
49
+ # and can thus not have a (precedence) conflict.
50
+ def set_precedence(p1, p2, precedenceRelation = :HIGHER)
51
+ return if p1 == p2
52
+ @in_conflict[p1] = true; @in_conflict[p2] = true
53
+ if precedenceRelation == :LOWER
54
+ precedenceRelation = :HIGHER
55
+ p1, p2 = p2, p1
56
+ end
57
+ if (precedenceRelation == :HIGHER and
58
+ (higher_precedence?(p2,p1) or equal_precedence?(p2,p1))) or
59
+ (precedenceRelation == :EQUAL and
60
+ (higher_precedence?(p2,p1) or higher_precedence?(p1,p2)))
61
+ raise RelationCircularityException
62
+ end
63
+ transitivity(p1, p2, precedenceRelation)
64
+ set_basic_precedence(p1, p2, precedence_hash(precedenceRelation))
65
+ if :EQUAL == precedenceRelation
66
+ set_basic_precedence(p2, p1, precedence_hash(precedenceRelation))
67
+ end
68
+ end
69
+
70
+ def higher_precedence(a)
71
+ @higher[a] || []
72
+ end
73
+
74
+ def higher_precedence?(a,b)
75
+ higher_precedence(a).include?(b)
76
+ end
77
+
78
+ def equal_precedence(a)
79
+ @equal[a] || []
80
+ end
81
+
82
+ def equal_precedence?(a,b)
83
+ equal_precedence(a).include?(b) or equal_precedence(b).include?(a)
84
+ end
85
+
86
+ def set_associativity(production1, production2, associativityRelation)
87
+ @in_conflict[production1] = true; @in_conflict[production2] = true
88
+ if associativityRelation == :LEFT
89
+ associativity_hash = @left
90
+ elsif associativityRelation == :RIGHT
91
+ associativity_hash = @right
92
+ else
93
+ associativity_hash = @non_associativity
94
+ end
95
+ begin
96
+ associativity_hash[production1].push production2
97
+ rescue NameError
98
+ associativity_hash[production1] = [production2]
99
+ end
100
+ end
101
+
102
+ def in_some_conflict?(production)
103
+ @in_conflict[production]
104
+ end
105
+
106
+ # Is there a conflict with having a child derived from 'subTreeProduction'
107
+ # at position 'position' in 'production'?
108
+ def conflict?(subTreeProduction, position, production)
109
+ # If production yielding subtree has higher precedence
110
+ # than production yielding root node or left or right associativity
111
+ # conflict
112
+ precedence_conflict?(subTreeProduction, production) or
113
+ left_or_non_associativity_conflict?(subTreeProduction, position,
114
+ production) or
115
+ right_or_non_associativity_conflict?(subTreeProduction, position,
116
+ production)
117
+ end
118
+
119
+ def precedence_conflict?(subTreeProduction, production)
120
+ higher_precedence?(production, subTreeProduction)
121
+ end
122
+
123
+ def left_or_non_associativity_conflict?(subTreeProduction, position, production)
124
+ if position == 0 or
125
+ position != production.elements.length-1
126
+ # No conflict unless the position is rightmost and there is something
127
+ # preceding it
128
+ false
129
+ else
130
+ left_associative?(subTreeProduction, production) or
131
+ non_associative?(subTreeProduction, production)
132
+ end
133
+ end
134
+
135
+ def right_or_non_associativity_conflict?(subTreeProduction, position,
136
+ production)
137
+ if position != 0 or
138
+ position == production.elements.length-1
139
+ # No conflict unless the position is leftmost and there is something
140
+ # after it
141
+ false
142
+ else
143
+ right_associative?(subTreeProduction, production) or
144
+ non_associative?(subTreeProduction, production)
145
+ end
146
+ end
147
+
148
+ def each(&block)
149
+ each_production_pair(@left, :LEFT, &block)
150
+ each_production_pair(@right, :RIGHT, &block)
151
+ each_production_pair(@non_associativity, :NONASSOC, &block)
152
+ each_production_pair(@higher, :HIGHER, &block)
153
+ each_production_pair(@equal, :EQUAL, &block)
154
+ end
155
+
156
+ def each_production_pair(hash, relation, &block)
157
+ hash.each do |p1, ps|
158
+ ps.each do |p2|
159
+ block.call(Relation.new(p1, relation, p2))
160
+ end
161
+ end
162
+ end
163
+
164
+ def to_src(name = nil, nameHash = {})
165
+ str = relations.to_src("relations", nameHash) + "\n"
166
+ str + assign_to(name, new_of_my_type(as_code("relations")))
167
+ end
168
+
169
+ def relations
170
+ relations = Array.new
171
+ self.each {|relation| relations.push relation}
172
+ relations
173
+ end
174
+
175
+ protected
176
+
177
+ def set_basic_precedence(a, b, hash)
178
+ begin
179
+ hash[a].push(b) unless hash[a].include?(b)
180
+ rescue NameError
181
+ hash[a] = [b]
182
+ end
183
+ end
184
+
185
+ def precedence_hash(relation)
186
+ case relation
187
+ when :HIGHER
188
+ @higher
189
+ when :EQUAL
190
+ @equal
191
+ else
192
+ raise ArgumentError, "Invalid relation #{relation.inspect}"
193
+ end
194
+ end
195
+
196
+ def transitivity(a, b, relation)
197
+ return if @transitivity_stack.include?([a,b,relation])
198
+ @transitivity_stack.push [a,b,relation]
199
+ higher_precedence(b).each {|c| set_precedence(a,c, :HIGHER)}
200
+ one_step_higher_than(a).each {|h| set_precedence(h,b, :HIGHER)}
201
+ equal_precedence(a).each {|c| set_precedence(c,b, relation)}
202
+ equal_precedence(b).each {|c| set_precedence(a,c, relation)}
203
+ if relation == :EQUAL
204
+ higher_precedence(a).each {|c| set_precedence(b,c, :HIGHER)}
205
+ end
206
+ @transitivity_stack.pop
207
+ end
208
+
209
+ def one_step_higher_than(a)
210
+ @higher.keys.select {|p| p != a and higher_precedence?(p, a)}
211
+ end
212
+
213
+ def init_relations(relations)
214
+ relations.each do |relation|
215
+ case relation.relation
216
+ when :LEFT, :RIGHT, :NONASSOC
217
+ set_associativity(relation.left, relation.right, relation.relation)
218
+ when :HIGHER, :LOWER, :EQUAL
219
+ set_precedence(relation.left, relation.right, relation.relation)
220
+ else
221
+ raise ArgumentError, "Unknown relation #{relation.inspect}"
222
+ end
223
+ end
224
+ end
225
+
226
+ def left_associative?(production1, production2)
227
+ check_associativity(production1, production2, @left) or
228
+ check_associativity(production2, production1, @left)
229
+ end
230
+
231
+ def right_associative?(production1, production2)
232
+ check_associativity(production1, production2, @right) or
233
+ check_associativity(production2, production1, @right)
234
+ end
235
+
236
+ def non_associative?(production1, production2)
237
+ check_associativity(production1, production2, @non_associativity) or
238
+ check_associativity(production2, production1, @non_associativity)
239
+ end
240
+
241
+ def check_associativity(production1, production2, associativityHash)
242
+ begin
243
+ associativityHash[production1].include?(production2)
244
+ rescue NameError
245
+ false
246
+ end
247
+ end
248
+
249
+ def precedence(production1, production2)
250
+ return :EQUAL if production1 == production2
251
+ begin
252
+ @precedence[production1][production2]
253
+ rescue NameError
254
+ nil # For the case when production1 is not related to anyone
255
+ end
256
+ end
257
+ end
258
+
259
+ # Short hand funcs for specifying priorities
260
+ def priorities(relations = [], *rest)
261
+ relations += rest
262
+ ProductionPriorities.new(relations.flatten.flatten)
263
+ end
264
+
265
+ def left(p1, *rest)
266
+ assocs(:LEFT, true, p1, *rest)
267
+ end
268
+
269
+ def right(p1, *rest)
270
+ assocs(:RIGHT, true, p1, *rest)
271
+ end
272
+
273
+ def nonassoc(p1, *rest)
274
+ assocs(:NONASSOC, true, p1, *rest)
275
+ end
276
+
277
+ def assocs(rel, allPairs, p1, *rest)
278
+ return assocs(rel, allPairs, *(p1+rest)) if p1.kind_of?(Array)
279
+ if rest.length == 0
280
+ assocs(rel, allPairs, p1, p1)
281
+ elsif rest.length == 1
282
+ [Relation.new(p1, rel, rest[0])]
283
+ else
284
+ if allPairs
285
+ rest.map {|prod| assocs(rel, allPairs, p1, prod)} +
286
+ assocs(rel, allPairs, rest.shift, *rest)
287
+ else
288
+ rest.unshift p1
289
+ (0...(rest.length-1)).map do |i|
290
+ assocs(rel, allPairs, rest[i], rest[i+1])
291
+ end.flatten
292
+ end
293
+ end
294
+ end
295
+
296
+ def decreasing_precedence(p1, p2, *rest)
297
+ assocs(:HIGHER, false, p1, p2, *rest)
298
+ end
299
+
300
+ def equal_precedence(p1, p2, *rest)
301
+ assocs(:EQUAL, false, p1, p2, *rest)
302
+ end