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.
- data/History.txt +5 -0
- data/LICENCE +515 -0
- data/Manifest.txt +126 -0
- data/README.txt +30 -0
- data/Rakefile +24 -0
- data/bin/rpdf2txt +58 -0
- data/config.save +12 -0
- data/install.rb +1098 -0
- data/lib/rpdf2txt-rockit/base_extensions.rb +73 -0
- data/lib/rpdf2txt-rockit/bootstrap.rb +120 -0
- data/lib/rpdf2txt-rockit/bounded_lru_cache.rb +43 -0
- data/lib/rpdf2txt-rockit/conflict_resolution.rb +302 -0
- data/lib/rpdf2txt-rockit/directed_graph.rb +401 -0
- data/lib/rpdf2txt-rockit/glr_parser.rb +393 -0
- data/lib/rpdf2txt-rockit/grammar.rb +644 -0
- data/lib/rpdf2txt-rockit/graphdrawing.rb +107 -0
- data/lib/rpdf2txt-rockit/graphviz_dot.rb +63 -0
- data/lib/rpdf2txt-rockit/indexable.rb +53 -0
- data/lib/rpdf2txt-rockit/lalr_parsetable_generator.rb +144 -0
- data/lib/rpdf2txt-rockit/parse_table.rb +273 -0
- data/lib/rpdf2txt-rockit/parsetable_generation.rb +164 -0
- data/lib/rpdf2txt-rockit/parsing_ambiguities.rb +84 -0
- data/lib/rpdf2txt-rockit/profiler.rb +168 -0
- data/lib/rpdf2txt-rockit/reduce_actions_generator.rb +523 -0
- data/lib/rpdf2txt-rockit/rockit.rb +76 -0
- data/lib/rpdf2txt-rockit/rockit_grammar_ast_eval.rb +187 -0
- data/lib/rpdf2txt-rockit/rockit_grammars_parser.rb +126 -0
- data/lib/rpdf2txt-rockit/sourcecode_dumpable.rb +181 -0
- data/lib/rpdf2txt-rockit/stringscanner.rb +54 -0
- data/lib/rpdf2txt-rockit/syntax_tree.rb +452 -0
- data/lib/rpdf2txt-rockit/token.rb +364 -0
- data/lib/rpdf2txt-rockit/version.rb +3 -0
- data/lib/rpdf2txt/attributesparser.rb +42 -0
- data/lib/rpdf2txt/cmapparser.rb +65 -0
- data/lib/rpdf2txt/data/_cmap.grammar +11 -0
- data/lib/rpdf2txt/data/_cmap_range.grammar +15 -0
- data/lib/rpdf2txt/data/_pdfattributes.grammar +32 -0
- data/lib/rpdf2txt/data/cmap.grammar +11 -0
- data/lib/rpdf2txt/data/cmap.rb +37 -0
- data/lib/rpdf2txt/data/cmap_range.grammar +15 -0
- data/lib/rpdf2txt/data/cmap_range.rb +43 -0
- data/lib/rpdf2txt/data/fonts/Courier-Bold.afm +342 -0
- data/lib/rpdf2txt/data/fonts/Courier-BoldOblique.afm +342 -0
- data/lib/rpdf2txt/data/fonts/Courier-Oblique.afm +342 -0
- data/lib/rpdf2txt/data/fonts/Courier.afm +342 -0
- data/lib/rpdf2txt/data/fonts/Helvetica-Bold.afm +2827 -0
- data/lib/rpdf2txt/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/lib/rpdf2txt/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/lib/rpdf2txt/data/fonts/Helvetica.afm +3051 -0
- data/lib/rpdf2txt/data/fonts/License-Adobe.txt +65 -0
- data/lib/rpdf2txt/data/fonts/Symbol.afm +213 -0
- data/lib/rpdf2txt/data/fonts/Times-Bold.afm +2588 -0
- data/lib/rpdf2txt/data/fonts/Times-BoldItalic.afm +2384 -0
- data/lib/rpdf2txt/data/fonts/Times-Italic.afm +2667 -0
- data/lib/rpdf2txt/data/fonts/Times-Roman.afm +2419 -0
- data/lib/rpdf2txt/data/fonts/ZapfDingbats.afm +225 -0
- data/lib/rpdf2txt/data/pdfattributes.grammar +32 -0
- data/lib/rpdf2txt/data/pdfattributes.rb +71 -0
- data/lib/rpdf2txt/data/pdftext.grammar +102 -0
- data/lib/rpdf2txt/data/pdftext.rb +146 -0
- data/lib/rpdf2txt/default_handler.rb +352 -0
- data/lib/rpdf2txt/lzw.rb +69 -0
- data/lib/rpdf2txt/object.rb +1114 -0
- data/lib/rpdf2txt/parser.rb +169 -0
- data/lib/rpdf2txt/symbol.rb +408 -0
- data/lib/rpdf2txt/text.rb +182 -0
- data/lib/rpdf2txt/text_state.rb +434 -0
- data/lib/rpdf2txt/textparser.rb +42 -0
- data/test/data/3392_obj +0 -0
- data/test/data/397_decrypted +15 -0
- data/test/data/450_decrypted +153 -0
- data/test/data/450_obj +0 -0
- data/test/data/452_decrypted +125 -0
- data/test/data/454_decrypted +108 -0
- data/test/data/456_decrypted +106 -0
- data/test/data/458_decrypted +111 -0
- data/test/data/458_obj +0 -0
- data/test/data/460_decrypted +118 -0
- data/test/data/460_obj +0 -0
- data/test/data/463_decrypted +117 -0
- data/test/data/465_decrypted +107 -0
- data/test/data/465_obj +0 -0
- data/test/data/90_obj +0 -0
- data/test/data/90_obj_comp +1 -0
- data/test/data/decrypted +0 -0
- data/test/data/encrypt_obj +0 -0
- data/test/data/encrypt_string +0 -0
- data/test/data/encrypt_string_128bit +0 -0
- data/test/data/encrypted_object_stream.pdf +0 -0
- data/test/data/firststream +1 -0
- data/test/data/index.pdfobj +0 -0
- data/test/data/index_2bit.pdfobj +0 -0
- data/test/data/index_masked.pdfobj +0 -0
- data/test/data/indexed.pdfobj +0 -0
- data/test/data/indexed_2bit.pdfobj +0 -0
- data/test/data/indexed_masked.pdfobj +0 -0
- data/test/data/inline.png +0 -0
- data/test/data/logo.png +0 -0
- data/test/data/lzw.pdfobj +0 -0
- data/test/data/lzw_index.pdfobj +0 -0
- data/test/data/page_tree.pdf +148 -0
- data/test/data/pdf_20.png +0 -0
- data/test/data/pdf_21.png +0 -0
- data/test/data/pdf_22.png +0 -0
- data/test/data/pdf_50.png +0 -0
- data/test/data/png.pdfobj +0 -0
- data/test/data/space_bug_stream.txt +119 -0
- data/test/data/stream.txt +292 -0
- data/test/data/stream_kerning_bug.txt +13 -0
- data/test/data/stream_kerning_bug2.txt +6 -0
- data/test/data/test.pdf +0 -0
- data/test/data/test.txt +8 -0
- data/test/data/test_text.txt +42 -0
- data/test/data/working_obj +0 -0
- data/test/data/working_obj2 +0 -0
- data/test/mock.rb +149 -0
- data/test/suite.rb +30 -0
- data/test/test_pdf_object.rb +1802 -0
- data/test/test_pdf_parser.rb +1340 -0
- data/test/test_pdf_text.rb +789 -0
- data/test/test_space_bug_05_2004.rb +87 -0
- data/test/test_stream.rb +194 -0
- data/test/test_text_state.rb +315 -0
- data/usage-en.txt +112 -0
- data/user-stories/UserStories_Rpdf2Txt.txt +34 -0
- data/user-stories/documents/swissmedicjournal/04_2004.pdf +0 -0
- 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
|